mirror of
https://github.com/juanfont/headscale.git
synced 2025-03-13 07:00:06 -04:00
Merge branch 'main' into patch-1
This commit is contained in:
commit
df10dd9c21
@ -15,3 +15,4 @@ README.md
|
|||||||
LICENSE
|
LICENSE
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
*.sock
|
||||||
|
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
name: "Bug report"
|
||||||
|
about: "Create a bug report to help us improve"
|
||||||
|
title: ""
|
||||||
|
labels: ["bug"]
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
**Bug description**
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the bug is. Describe the expected bahavior
|
||||||
|
and how it is currently different. If you are unsure if it is a bug, consider discussing
|
||||||
|
it on our Discord server first. -->
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
|
||||||
|
<!-- Steps to reproduce the behavior. -->
|
||||||
|
|
||||||
|
**Context info**
|
||||||
|
|
||||||
|
<!-- Please add relevant information about your system. For example:
|
||||||
|
- Version of headscale used
|
||||||
|
- Version of tailscale client
|
||||||
|
- OS (e.g. Linux, Mac, Cygwin, WSL, etc.) and version
|
||||||
|
- Kernel version
|
||||||
|
- The relevant config parameters you used
|
||||||
|
- Log output
|
||||||
|
-->
|
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Issues must have some content
|
||||||
|
blank_issues_enabled: false
|
||||||
|
|
||||||
|
# Contact links
|
||||||
|
contact_links:
|
||||||
|
- name: "headscale usage documentation"
|
||||||
|
url: "https://github.com/juanfont/headscale/blob/main/docs"
|
||||||
|
about: "Find documentation about how to configure and run headscale."
|
||||||
|
- name: "headscale Discord community"
|
||||||
|
url: "https://discord.com/invite/XcQxk2VHjx"
|
||||||
|
about: "Please ask and answer questions about usage of headscale here."
|
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
name: "Feature request"
|
||||||
|
about: "Suggest an idea for headscale"
|
||||||
|
title: ""
|
||||||
|
labels: ["enhancement"]
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
**Feature request**
|
||||||
|
|
||||||
|
<!-- A clear and precise description of what new or changed feature you want. -->
|
||||||
|
|
||||||
|
<!-- Please include the reason, why you would need the feature. E.g. what problem
|
||||||
|
does it solve? Or which workflow is currently frustrating and will be improved by
|
||||||
|
this? -->
|
28
.github/ISSUE_TEMPLATE/other_issue.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/other_issue.md
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
name: "Other issue"
|
||||||
|
about: "Report a different issue"
|
||||||
|
title: ""
|
||||||
|
labels: ["bug"]
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- If you have a question, please consider using our Discord for asking questions -->
|
||||||
|
|
||||||
|
**Issue description**
|
||||||
|
|
||||||
|
<!-- Please add your issue description. -->
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
|
||||||
|
<!-- Steps to reproduce the behavior. -->
|
||||||
|
|
||||||
|
**Context info**
|
||||||
|
|
||||||
|
<!-- Please add relevant information about your system. For example:
|
||||||
|
- Version of headscale used
|
||||||
|
- Version of tailscale client
|
||||||
|
- OS (e.g. Linux, Mac, Cygwin, WSL, etc.) and version
|
||||||
|
- Kernel version
|
||||||
|
- The relevant config parameters you used
|
||||||
|
- Log output
|
||||||
|
-->
|
10
.github/pull_request_template.md
vendored
Normal file
10
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!-- Please tick if the following things apply. You… -->
|
||||||
|
|
||||||
|
- [] read the [CONTRIBUTING guidelines](README.md#user-content-contributing)
|
||||||
|
- [] raised a GitHub issue or discussed it on the projects chat beforehand
|
||||||
|
- [] added unit tests
|
||||||
|
- [] added integration tests
|
||||||
|
- [] updated documentation if needed
|
||||||
|
- [] updated CHANGELOG.md
|
||||||
|
|
||||||
|
<!-- If applicable, please reference the issue using `Fixes #XXX` and add tests to cover your new code. -->
|
21
.github/workflows/build.yml
vendored
21
.github/workflows/build.yml
vendored
@ -14,23 +14,38 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
go.*
|
||||||
|
**/*.go
|
||||||
|
integration_test/
|
||||||
|
config-example.yaml
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "1.16.3"
|
go-version: "1.17"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
go version
|
go version
|
||||||
go install golang.org/x/lint/golint@latest
|
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y make
|
sudo apt install -y make
|
||||||
|
|
||||||
- name: Run lint
|
- name: Run build
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: make build
|
run: make build
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
with:
|
with:
|
||||||
name: headscale-linux
|
name: headscale-linux
|
||||||
path: headscale
|
path: headscale
|
||||||
|
87
.github/workflows/lint.yml
vendored
87
.github/workflows/lint.yml
vendored
@ -1,39 +1,74 @@
|
|||||||
|
---
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# The "build" workflow
|
golangci-lint:
|
||||||
lint:
|
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
||||||
steps:
|
steps:
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
# Install and run golangci-lint as a separate step, it's much faster this
|
|
||||||
# way because this action has caching. It'll get run again in `make lint`
|
|
||||||
# below, but it's still much faster in the end than installing
|
|
||||||
# golangci-lint manually in the `Run lint` step.
|
|
||||||
- uses: golangci/golangci-lint-action@v2
|
|
||||||
with:
|
with:
|
||||||
args: --timeout 5m
|
fetch-depth: 2
|
||||||
|
|
||||||
# Setup Go
|
- name: Get changed files
|
||||||
- name: Setup Go
|
id: changed-files
|
||||||
uses: actions/setup-go@v2
|
uses: tj-actions/changed-files@v14.1
|
||||||
with:
|
with:
|
||||||
go-version: "1.16.3" # The Go version to download (if necessary) and use.
|
files: |
|
||||||
|
go.*
|
||||||
|
**/*.go
|
||||||
|
integration_test/
|
||||||
|
config-example.yaml
|
||||||
|
|
||||||
# Install all the dependencies
|
- name: golangci-lint
|
||||||
- name: Install dependencies
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: |
|
uses: golangci/golangci-lint-action@v2
|
||||||
go version
|
with:
|
||||||
go install golang.org/x/lint/golint@latest
|
version: latest
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y make
|
|
||||||
|
|
||||||
- name: Run lint
|
# Only block PRs on new problems.
|
||||||
run: make lint
|
# If this is not enabled, we will end up having PRs
|
||||||
|
# blocked because new linters has appared and other
|
||||||
|
# parts of the code is affected.
|
||||||
|
only-new-issues: true
|
||||||
|
|
||||||
|
prettier-lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
**/*.md
|
||||||
|
**/*.yml
|
||||||
|
**/*.yaml
|
||||||
|
**/*.ts
|
||||||
|
**/*.js
|
||||||
|
**/*.sass
|
||||||
|
**/*.css
|
||||||
|
**/*.scss
|
||||||
|
**/*.html
|
||||||
|
|
||||||
|
- name: Prettify code
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
uses: creyD/prettier_action@v4.0
|
||||||
|
with:
|
||||||
|
prettier_options: >-
|
||||||
|
--check **/*.{ts,js,md,yaml,yml,sass,css,scss,html}
|
||||||
|
only_changed: false
|
||||||
|
dry: true
|
||||||
|
|
||||||
|
proto-lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: bufbuild/buf-setup-action@v0.7.0
|
||||||
|
- uses: bufbuild/buf-lint-action@v1
|
||||||
|
with:
|
||||||
|
input: "proto"
|
||||||
|
151
.github/workflows/release.yml
vendored
151
.github/workflows/release.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.16
|
go-version: 1.17
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
@ -40,6 +40,19 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v3
|
uses: docker/metadata-action@v3
|
||||||
@ -52,6 +65,7 @@ jobs:
|
|||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
|
type=raw,value=latest
|
||||||
type=sha
|
type=sha
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v1
|
||||||
@ -72,3 +86,138 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
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
|
||||||
|
- 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@v2
|
||||||
|
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: |
|
||||||
|
latest=false
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}-debug
|
||||||
|
type=semver,pattern={{major}}.{{minor}}-debug
|
||||||
|
type=semver,pattern={{major}}-debug
|
||||||
|
type=raw,value=latest-debug
|
||||||
|
type=sha,suffix=-debug
|
||||||
|
- 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
|
||||||
|
- name: Prepare cache for next build
|
||||||
|
run: |
|
||||||
|
rm -rf /tmp/.buildx-cache-debug
|
||||||
|
mv /tmp/.buildx-cache-debug-new /tmp/.buildx-cache-debug
|
||||||
|
|
||||||
|
docker-alpine-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
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-alpine
|
||||||
|
key: ${{ runner.os }}-buildx-alpine-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-buildx-alpine-
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta-alpine
|
||||||
|
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: |
|
||||||
|
latest=false
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}-alpine
|
||||||
|
type=semver,pattern={{major}}.{{minor}}-alpine
|
||||||
|
type=semver,pattern={{major}}-alpine
|
||||||
|
type=raw,value=latest-alpine
|
||||||
|
type=sha,suffix=-alpine
|
||||||
|
- 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.alpine
|
||||||
|
tags: ${{ steps.meta-alpine.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta-alpine.outputs.labels }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache-alpine
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache-alpine-new
|
||||||
|
- name: Prepare cache for next build
|
||||||
|
run: |
|
||||||
|
rm -rf /tmp/.buildx-cache-alpine
|
||||||
|
mv /tmp/.buildx-cache-alpine-new /tmp/.buildx-cache-alpine
|
||||||
|
21
.github/workflows/test-integration.yml
vendored
21
.github/workflows/test-integration.yml
vendored
@ -3,21 +3,30 @@ name: CI
|
|||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# The "build" workflow
|
|
||||||
integration-test:
|
integration-test:
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
||||||
steps:
|
steps:
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
go.*
|
||||||
|
**/*.go
|
||||||
|
integration_test/
|
||||||
|
config-example.yaml
|
||||||
|
|
||||||
# Setup Go
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "1.16.3"
|
go-version: "1.17"
|
||||||
|
|
||||||
- name: Run Integration tests
|
- name: Run Integration tests
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: go test -tags integration -timeout 30m
|
run: go test -tags integration -timeout 30m
|
||||||
|
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
@ -3,31 +3,41 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# The "build" workflow
|
|
||||||
test:
|
test:
|
||||||
# The type of runner that the job will run on
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
|
||||||
steps:
|
steps:
|
||||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Get changed files
|
||||||
|
id: changed-files
|
||||||
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
go.*
|
||||||
|
**/*.go
|
||||||
|
integration_test/
|
||||||
|
config-example.yaml
|
||||||
|
|
||||||
# Setup Go
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "1.16.3" # The Go version to download (if necessary) and use.
|
go-version: "1.17"
|
||||||
|
|
||||||
# Install all the dependencies
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
go version
|
go version
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install -y make
|
sudo apt install -y make
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: make test
|
run: make test
|
||||||
|
|
||||||
- name: Run build
|
- name: Run build
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: make
|
run: make
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -17,6 +17,8 @@
|
|||||||
/headscale
|
/headscale
|
||||||
config.json
|
config.json
|
||||||
config.yaml
|
config.yaml
|
||||||
|
derp.yaml
|
||||||
|
*.hujson
|
||||||
*.key
|
*.key
|
||||||
/db.sqlite
|
/db.sqlite
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
56
.golangci.yaml
Normal file
56
.golangci.yaml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
run:
|
||||||
|
timeout: 10m
|
||||||
|
|
||||||
|
issues:
|
||||||
|
skip-dirs:
|
||||||
|
- gen
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- exhaustivestruct
|
||||||
|
- revive
|
||||||
|
- lll
|
||||||
|
- interfacer
|
||||||
|
- scopelint
|
||||||
|
- maligned
|
||||||
|
- golint
|
||||||
|
- gofmt
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
- gocognit
|
||||||
|
- funlen
|
||||||
|
- exhaustivestruct
|
||||||
|
- tagliatelle
|
||||||
|
- godox
|
||||||
|
- ireturn
|
||||||
|
|
||||||
|
# We should strive to enable these:
|
||||||
|
- wrapcheck
|
||||||
|
- dupl
|
||||||
|
- makezero
|
||||||
|
|
||||||
|
# We might want to enable this, but it might be a lot of work
|
||||||
|
- cyclop
|
||||||
|
- nestif
|
||||||
|
- wsl # might be incompatible with gofumpt
|
||||||
|
- testpackage
|
||||||
|
- paralleltest
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
varnamelen:
|
||||||
|
ignore-type-assert-ok: true
|
||||||
|
ignore-map-index-ok: true
|
||||||
|
ignore-names:
|
||||||
|
- err
|
||||||
|
- db
|
||||||
|
- id
|
||||||
|
- ip
|
||||||
|
- ok
|
||||||
|
- c
|
||||||
|
|
||||||
|
gocritic:
|
||||||
|
disabled-checks:
|
||||||
|
- appendAssign
|
||||||
|
# TODO(kradalby): Remove this
|
||||||
|
- ifElseChain
|
@ -1,8 +1,11 @@
|
|||||||
# This is an example .goreleaser.yml file with some sane defaults.
|
---
|
||||||
# Make sure to check the documentation at http://goreleaser.com
|
|
||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod tidy
|
- go mod tidy -compat=1.17
|
||||||
|
|
||||||
|
release:
|
||||||
|
prerelease: auto
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- id: darwin-amd64
|
- id: darwin-amd64
|
||||||
main: ./cmd/headscale/headscale.go
|
main: ./cmd/headscale/headscale.go
|
||||||
@ -29,7 +32,7 @@ builds:
|
|||||||
goarch:
|
goarch:
|
||||||
- arm
|
- arm
|
||||||
goarm:
|
goarm:
|
||||||
- 7
|
- "7"
|
||||||
env:
|
env:
|
||||||
- CC=arm-linux-gnueabihf-gcc
|
- CC=arm-linux-gnueabihf-gcc
|
||||||
- CXX=arm-linux-gnueabihf-g++
|
- CXX=arm-linux-gnueabihf-g++
|
||||||
|
60
CHANGELOG.md
Normal file
60
CHANGELOG.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
**TBD (TBD):**
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
|
||||||
|
**0.12.4 (2022-01-29):**
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
|
||||||
|
- Make gRPC Unix Socket permissions configurable [#292](https://github.com/juanfont/headscale/pull/292)
|
||||||
|
- Trim whitespace before reading Private Key from file [#289](https://github.com/juanfont/headscale/pull/289)
|
||||||
|
- Add new command to generate a private key for `headscale` [#290](https://github.com/juanfont/headscale/pull/290)
|
||||||
|
- Fixed issue where hosts deleted from control server may be written back to the database, as long as they are connected to the control server [#278](https://github.com/juanfont/headscale/pull/278)
|
||||||
|
|
||||||
|
**0.12.3 (2022-01-13):**
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
|
||||||
|
- Added Alpine container [#270](https://github.com/juanfont/headscale/pull/270)
|
||||||
|
- Minor updates in dependencies [#271](https://github.com/juanfont/headscale/pull/271)
|
||||||
|
|
||||||
|
**0.12.2 (2022-01-11):**
|
||||||
|
|
||||||
|
Happy New Year!
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
|
||||||
|
- Fix Docker release [#258](https://github.com/juanfont/headscale/pull/258)
|
||||||
|
- Rewrite main docs [#262](https://github.com/juanfont/headscale/pull/262)
|
||||||
|
- Improve Docker docs [#263](https://github.com/juanfont/headscale/pull/263)
|
||||||
|
|
||||||
|
**0.12.1 (2021-12-24):**
|
||||||
|
|
||||||
|
(We are skipping 0.12.0 to correct a mishap done weeks ago with the version tagging)
|
||||||
|
|
||||||
|
**BREAKING**:
|
||||||
|
|
||||||
|
- Upgrade to Tailscale 1.18 [#229](https://github.com/juanfont/headscale/pull/229)
|
||||||
|
- This change requires a new format for private key, private keys are now generated automatically:
|
||||||
|
1. Delete your current key
|
||||||
|
2. Restart `headscale`, a new key will be generated.
|
||||||
|
3. Restart all Tailscale clients to fetch the new key
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
|
||||||
|
- Unify configuration example [#197](https://github.com/juanfont/headscale/pull/197)
|
||||||
|
- Add stricter linting and formatting [#223](https://github.com/juanfont/headscale/pull/223)
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
|
||||||
|
- Add gRPC and HTTP API (HTTP API is currently disabled) [#204](https://github.com/juanfont/headscale/pull/204)
|
||||||
|
- Use gRPC between the CLI and the server [#206](https://github.com/juanfont/headscale/pull/206), [#212](https://github.com/juanfont/headscale/pull/212)
|
||||||
|
- Beta OpenID Connect support [#126](https://github.com/juanfont/headscale/pull/126), [#227](https://github.com/juanfont/headscale/pull/227)
|
||||||
|
|
||||||
|
**0.11.0 (2021-10-25):**
|
||||||
|
|
||||||
|
**BREAKING**:
|
||||||
|
|
||||||
|
- Make headscale fetch DERP map from URL and file [#196](https://github.com/juanfont/headscale/pull/196)
|
18
Dockerfile
18
Dockerfile
@ -1,23 +1,21 @@
|
|||||||
FROM golang:1.17.1-bullseye AS build
|
# Builder image
|
||||||
|
FROM golang:1.17.6-bullseye AS build
|
||||||
ENV GOPATH /go
|
ENV GOPATH /go
|
||||||
|
WORKDIR /go/src/headscale
|
||||||
|
|
||||||
COPY go.mod go.sum /go/src/headscale/
|
COPY go.mod go.sum /go/src/headscale/
|
||||||
WORKDIR /go/src/headscale
|
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
COPY . /go/src/headscale
|
COPY . .
|
||||||
|
|
||||||
RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale
|
RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale
|
||||||
|
RUN strip /go/bin/headscale
|
||||||
RUN test -e /go/bin/headscale
|
RUN test -e /go/bin/headscale
|
||||||
|
|
||||||
FROM ubuntu:20.04
|
# Production image
|
||||||
|
FROM gcr.io/distroless/base-debian11
|
||||||
|
|
||||||
RUN apt-get update \
|
COPY --from=build /go/bin/headscale /bin/headscale
|
||||||
&& apt-get install -y ca-certificates \
|
|
||||||
&& update-ca-certificates \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
COPY --from=build /go/bin/headscale /usr/local/bin/headscale
|
|
||||||
ENV TZ UTC
|
ENV TZ UTC
|
||||||
|
|
||||||
EXPOSE 8080/tcp
|
EXPOSE 8080/tcp
|
||||||
|
23
Dockerfile.alpine
Normal file
23
Dockerfile.alpine
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Builder image
|
||||||
|
FROM golang:1.17.6-alpine AS build
|
||||||
|
ENV GOPATH /go
|
||||||
|
WORKDIR /go/src/headscale
|
||||||
|
|
||||||
|
COPY go.mod go.sum /go/src/headscale/
|
||||||
|
RUN apk add gcc musl-dev
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale
|
||||||
|
RUN strip /go/bin/headscale
|
||||||
|
RUN test -e /go/bin/headscale
|
||||||
|
|
||||||
|
# Production image
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
COPY --from=build /go/bin/headscale /bin/headscale
|
||||||
|
ENV TZ UTC
|
||||||
|
|
||||||
|
EXPOSE 8080/tcp
|
||||||
|
CMD ["headscale"]
|
23
Dockerfile.debug
Normal file
23
Dockerfile.debug
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Builder image
|
||||||
|
FROM golang:1.17.1-bullseye AS build
|
||||||
|
ENV GOPATH /go
|
||||||
|
WORKDIR /go/src/headscale
|
||||||
|
|
||||||
|
COPY go.mod go.sum /go/src/headscale/
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go install -a -ldflags="-extldflags=-static" -tags netgo,sqlite_omit_load_extension ./cmd/headscale
|
||||||
|
RUN test -e /go/bin/headscale
|
||||||
|
|
||||||
|
# Debug image
|
||||||
|
FROM gcr.io/distroless/base-debian11:debug
|
||||||
|
|
||||||
|
COPY --from=build /go/bin/headscale /bin/headscale
|
||||||
|
ENV TZ UTC
|
||||||
|
|
||||||
|
# Need to reset the entrypoint or everything will run as a busybox script
|
||||||
|
ENTRYPOINT []
|
||||||
|
EXPOSE 8080/tcp
|
||||||
|
CMD ["headscale"]
|
32
Makefile
32
Makefile
@ -1,6 +1,14 @@
|
|||||||
# Calculate version
|
# Calculate version
|
||||||
version = $(shell ./scripts/version-at-commit.sh)
|
version = $(shell ./scripts/version-at-commit.sh)
|
||||||
|
|
||||||
|
rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
|
||||||
|
|
||||||
|
# GO_SOURCES = $(wildcard *.go)
|
||||||
|
# PROTO_SOURCES = $(wildcard **/*.proto)
|
||||||
|
GO_SOURCES = $(call rwildcard,,*.go)
|
||||||
|
PROTO_SOURCES = $(call rwildcard,,*.proto)
|
||||||
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build -ldflags "-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$(version)" cmd/headscale/headscale.go
|
go build -ldflags "-s -w -X github.com/juanfont/headscale/cmd/headscale/cli.Version=$(version)" cmd/headscale/headscale.go
|
||||||
|
|
||||||
@ -12,6 +20,9 @@ test:
|
|||||||
test_integration:
|
test_integration:
|
||||||
go test -tags integration -timeout 30m ./...
|
go test -tags integration -timeout 30m ./...
|
||||||
|
|
||||||
|
test_integration_cli:
|
||||||
|
go test -tags integration -v integration_cli_test.go integration_common_test.go
|
||||||
|
|
||||||
coverprofile_func:
|
coverprofile_func:
|
||||||
go tool cover -func=coverage.out
|
go tool cover -func=coverage.out
|
||||||
|
|
||||||
@ -19,9 +30,26 @@ coverprofile_html:
|
|||||||
go tool cover -html=coverage.out
|
go tool cover -html=coverage.out
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
golint
|
golangci-lint run --fix --timeout 10m
|
||||||
golangci-lint run --timeout 5m
|
|
||||||
|
fmt:
|
||||||
|
prettier --write '**/**.{ts,js,md,yaml,yml,sass,css,scss,html}'
|
||||||
|
golines --max-len=88 --base-formatter=gofumpt -w $(GO_SOURCES)
|
||||||
|
clang-format -style="{BasedOnStyle: Google, IndentWidth: 4, AlignConsecutiveDeclarations: true, AlignConsecutiveAssignments: true, ColumnLimit: 0}" -i $(PROTO_SOURCES)
|
||||||
|
|
||||||
|
proto-lint:
|
||||||
|
cd proto/ && buf lint
|
||||||
|
|
||||||
compress: build
|
compress: build
|
||||||
upx --brute headscale
|
upx --brute headscale
|
||||||
|
|
||||||
|
generate:
|
||||||
|
rm -rf gen
|
||||||
|
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
|
||||||
|
80
README.md
80
README.md
@ -6,6 +6,8 @@ An open source, self-hosted implementation of the Tailscale coordination server.
|
|||||||
|
|
||||||
Join our [Discord](https://discord.gg/XcQxk2VHjx) server for a chat.
|
Join our [Discord](https://discord.gg/XcQxk2VHjx) server for a chat.
|
||||||
|
|
||||||
|
**Note:** Always select the same GitHub tag as the released version you use to ensure you have the correct example configuration and documentation. The `main` branch might contain unreleased changes.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/).
|
Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/).
|
||||||
@ -29,6 +31,7 @@ headscale implements this coordination server.
|
|||||||
- [x] Taildrop (File Sharing)
|
- [x] Taildrop (File Sharing)
|
||||||
- [x] Support for alternative IP ranges in the tailnets (default Tailscale's 100.64.0.0/10)
|
- [x] Support for alternative IP ranges in the tailnets (default Tailscale's 100.64.0.0/10)
|
||||||
- [x] DNS (passing DNS servers to nodes)
|
- [x] DNS (passing DNS servers to nodes)
|
||||||
|
- [x] Single-Sign-On (via Open ID Connect)
|
||||||
- [x] Share nodes between namespaces
|
- [x] Share nodes between namespaces
|
||||||
- [x] MagicDNS (see `docs/`)
|
- [x] MagicDNS (see `docs/`)
|
||||||
|
|
||||||
@ -47,17 +50,67 @@ headscale implements this coordination server.
|
|||||||
|
|
||||||
Suggestions/PRs welcomed!
|
Suggestions/PRs welcomed!
|
||||||
|
|
||||||
|
|
||||||
## Running headscale
|
## Running headscale
|
||||||
|
|
||||||
Please have a look at the documentation under [`docs/`](docs/).
|
Please have a look at the documentation under [`docs/`](docs/).
|
||||||
|
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
1. We have nothing to do with Tailscale, or Tailscale Inc.
|
1. We have nothing to do with Tailscale, or Tailscale Inc.
|
||||||
2. The purpose of writing this was to learn how Tailscale works.
|
2. The purpose of Headscale is maintaining a working, self-hosted Tailscale control panel.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
To contribute to Headscale you would need the lastest version of [Go](https://golang.org) and [Buf](https://buf.build)(Protobuf generator).
|
||||||
|
|
||||||
|
### Code style
|
||||||
|
|
||||||
|
To ensure we have some consistency with a growing number of contributions, this project has adopted linting and style/formatting rules:
|
||||||
|
|
||||||
|
The **Go** code is linted with [`golangci-lint`](https://golangci-lint.run) and
|
||||||
|
formatted with [`golines`](https://github.com/segmentio/golines) (width 88) and
|
||||||
|
[`gofumpt`](https://github.com/mvdan/gofumpt).
|
||||||
|
Please configure your editor to run the tools while developing and make sure to
|
||||||
|
run `make lint` and `make fmt` before committing any code.
|
||||||
|
|
||||||
|
The **Proto** code is linted with [`buf`](https://docs.buf.build/lint/overview) and
|
||||||
|
formatted with [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html).
|
||||||
|
|
||||||
|
The **rest** (Markdown, YAML, etc) is formatted with [`prettier`](https://prettier.io).
|
||||||
|
|
||||||
|
Check out the `.golangci.yaml` and `Makefile` to see the specific configuration.
|
||||||
|
|
||||||
|
### Install development tools
|
||||||
|
|
||||||
|
- Go
|
||||||
|
- Buf
|
||||||
|
- Protobuf tools:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make install-protobuf-plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing and building
|
||||||
|
|
||||||
|
Some parts of the project require the generation of Go code from Protobuf (if changes are made in `proto/`) and it must be (re-)generated with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make generate
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: Please check in changes from `gen/` in a separate commit to make it easier to review.
|
||||||
|
|
||||||
|
To run the tests:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
To build the program:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
@ -91,6 +144,13 @@ Please have a look at the documentation under [`docs/`](docs/).
|
|||||||
<sub style="font-size:14px"><b>ohdearaugustin</b></sub>
|
<sub style="font-size:14px"><b>ohdearaugustin</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
|
<a href=https://github.com/unreality>
|
||||||
|
<img src=https://avatars.githubusercontent.com/u/352522?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=unreality/>
|
||||||
|
<br />
|
||||||
|
<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">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/qbit>
|
<a href=https://github.com/qbit>
|
||||||
<img src=https://avatars.githubusercontent.com/u/68368?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Aaron Bieber/>
|
<img src=https://avatars.githubusercontent.com/u/68368?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Aaron Bieber/>
|
||||||
@ -98,6 +158,8 @@ Please have a look at the documentation under [`docs/`](docs/).
|
|||||||
<sub style="font-size:14px"><b>Aaron Bieber</b></sub>
|
<sub style="font-size:14px"><b>Aaron Bieber</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/ptman>
|
<a href=https://github.com/ptman>
|
||||||
<img src=https://avatars.githubusercontent.com/u/24669?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Paul Tötterman/>
|
<img src=https://avatars.githubusercontent.com/u/24669?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Paul Tötterman/>
|
||||||
@ -105,8 +167,6 @@ Please have a look at the documentation under [`docs/`](docs/).
|
|||||||
<sub style="font-size:14px"><b>Paul Tötterman</b></sub>
|
<sub style="font-size:14px"><b>Paul Tötterman</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/cmars>
|
<a href=https://github.com/cmars>
|
||||||
<img src=https://avatars.githubusercontent.com/u/23741?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Casey Marshall/>
|
<img src=https://avatars.githubusercontent.com/u/23741?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Casey Marshall/>
|
||||||
@ -142,6 +202,8 @@ Please have a look at the documentation under [`docs/`](docs/).
|
|||||||
<sub style="font-size:14px"><b>Felix Kronlage-Dammers</b></sub>
|
<sub style="font-size:14px"><b>Felix Kronlage-Dammers</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/felixonmars>
|
<a href=https://github.com/felixonmars>
|
||||||
<img src=https://avatars.githubusercontent.com/u/1006477?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Felix Yan/>
|
<img src=https://avatars.githubusercontent.com/u/1006477?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Felix Yan/>
|
||||||
@ -149,8 +211,6 @@ Please have a look at the documentation under [`docs/`](docs/).
|
|||||||
<sub style="font-size:14px"><b>Felix Yan</b></sub>
|
<sub style="font-size:14px"><b>Felix Yan</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/shaananc>
|
<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/>
|
<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/>
|
||||||
@ -186,6 +246,8 @@ Please have a look at the documentation under [`docs/`](docs/).
|
|||||||
<sub style="font-size:14px"><b>Tjerk Woudsma</b></sub>
|
<sub style="font-size:14px"><b>Tjerk Woudsma</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/zekker6>
|
<a href=https://github.com/zekker6>
|
||||||
<img src=https://avatars.githubusercontent.com/u/1367798?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Zakhar Bessarab/>
|
<img src=https://avatars.githubusercontent.com/u/1367798?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=Zakhar Bessarab/>
|
||||||
@ -193,8 +255,6 @@ Please have a look at the documentation under [`docs/`](docs/).
|
|||||||
<sub style="font-size:14px"><b>Zakhar Bessarab</b></sub>
|
<sub style="font-size:14px"><b>Zakhar Bessarab</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
<td align="center" style="word-wrap: break-word; width: 150.0; height: 150.0">
|
||||||
<a href=https://github.com/derelm>
|
<a href=https://github.com/derelm>
|
||||||
<img src=https://avatars.githubusercontent.com/u/465155?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=derelm/>
|
<img src=https://avatars.githubusercontent.com/u/465155?v=4 width="100;" style="border-radius:50%;align-items:center;justify-content:center;overflow:hidden;padding-top:10px" alt=derelm/>
|
||||||
@ -218,5 +278,3 @@ Please have a look at the documentation under [`docs/`](docs/).
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
196
acls.go
196
acls.go
@ -9,22 +9,39 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/tailscale/hujson"
|
"github.com/tailscale/hujson"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
const errorEmptyPolicy = Error("empty policy")
|
const (
|
||||||
const errorInvalidAction = Error("invalid action")
|
errEmptyPolicy = Error("empty policy")
|
||||||
const errorInvalidUserSection = Error("invalid user section")
|
errInvalidAction = Error("invalid action")
|
||||||
const errorInvalidGroup = Error("invalid group")
|
errInvalidUserSection = Error("invalid user section")
|
||||||
const errorInvalidTag = Error("invalid tag")
|
errInvalidGroup = Error("invalid group")
|
||||||
const errorInvalidNamespace = Error("invalid namespace")
|
errInvalidTag = Error("invalid tag")
|
||||||
const errorInvalidPortFormat = Error("invalid port format")
|
errInvalidNamespace = Error("invalid namespace")
|
||||||
|
errInvalidPortFormat = Error("invalid port format")
|
||||||
|
)
|
||||||
|
|
||||||
// LoadACLPolicy loads the ACL policy from the specify path, and generates the ACL rules
|
const (
|
||||||
|
Base8 = 8
|
||||||
|
Base10 = 10
|
||||||
|
BitSize16 = 16
|
||||||
|
BitSize32 = 32
|
||||||
|
BitSize64 = 64
|
||||||
|
portRangeBegin = 0
|
||||||
|
portRangeEnd = 65535
|
||||||
|
expectedTokenItems = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadACLPolicy loads the ACL policy from the specify path, and generates the ACL rules.
|
||||||
func (h *Headscale) LoadACLPolicy(path string) error {
|
func (h *Headscale) LoadACLPolicy(path string) error {
|
||||||
|
log.Debug().
|
||||||
|
Str("func", "LoadACLPolicy").
|
||||||
|
Str("path", path).
|
||||||
|
Msg("Loading ACL policy from path")
|
||||||
|
|
||||||
policyFile, err := os.Open(path)
|
policyFile, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -32,16 +49,23 @@ func (h *Headscale) LoadACLPolicy(path string) error {
|
|||||||
defer policyFile.Close()
|
defer policyFile.Close()
|
||||||
|
|
||||||
var policy ACLPolicy
|
var policy ACLPolicy
|
||||||
b, err := io.ReadAll(policyFile)
|
policyBytes, err := io.ReadAll(policyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = hujson.Unmarshal(b, &policy)
|
|
||||||
|
ast, err := hujson.Parse(policyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ast.Standardize()
|
||||||
|
policyBytes = ast.Pack()
|
||||||
|
err = json.Unmarshal(policyBytes, &policy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if policy.IsZero() {
|
if policy.IsZero() {
|
||||||
return errorEmptyPolicy
|
return errEmptyPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
h.aclPolicy = &policy
|
h.aclPolicy = &policy
|
||||||
@ -50,40 +74,45 @@ func (h *Headscale) LoadACLPolicy(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
h.aclRules = rules
|
h.aclRules = rules
|
||||||
|
|
||||||
|
log.Trace().Interface("ACL", rules).Msg("ACL rules generated")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) generateACLRules() (*[]tailcfg.FilterRule, error) {
|
func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
|
||||||
rules := []tailcfg.FilterRule{}
|
rules := []tailcfg.FilterRule{}
|
||||||
|
|
||||||
for i, a := range h.aclPolicy.ACLs {
|
for index, acl := range h.aclPolicy.ACLs {
|
||||||
if a.Action != "accept" {
|
if acl.Action != "accept" {
|
||||||
return nil, errorInvalidAction
|
return nil, errInvalidAction
|
||||||
}
|
}
|
||||||
|
|
||||||
r := tailcfg.FilterRule{}
|
filterRule := tailcfg.FilterRule{}
|
||||||
|
|
||||||
srcIPs := []string{}
|
srcIPs := []string{}
|
||||||
for j, u := range a.Users {
|
for innerIndex, user := range acl.Users {
|
||||||
srcs, err := h.generateACLPolicySrcIP(u)
|
srcs, err := h.generateACLPolicySrcIP(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Msgf("Error parsing ACL %d, User %d", i, j)
|
Msgf("Error parsing ACL %d, User %d", index, innerIndex)
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
srcIPs = append(srcIPs, *srcs...)
|
srcIPs = append(srcIPs, srcs...)
|
||||||
}
|
}
|
||||||
r.SrcIPs = srcIPs
|
filterRule.SrcIPs = srcIPs
|
||||||
|
|
||||||
destPorts := []tailcfg.NetPortRange{}
|
destPorts := []tailcfg.NetPortRange{}
|
||||||
for j, d := range a.Ports {
|
for innerIndex, ports := range acl.Ports {
|
||||||
dests, err := h.generateACLPolicyDestPorts(d)
|
dests, err := h.generateACLPolicyDestPorts(ports)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Msgf("Error parsing ACL %d, Port %d", i, j)
|
Msgf("Error parsing ACL %d, Port %d", index, innerIndex)
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
destPorts = append(destPorts, *dests...)
|
destPorts = append(destPorts, dests...)
|
||||||
}
|
}
|
||||||
|
|
||||||
rules = append(rules, tailcfg.FilterRule{
|
rules = append(rules, tailcfg.FilterRule{
|
||||||
@ -92,17 +121,19 @@ func (h *Headscale) generateACLRules() (*[]tailcfg.FilterRule, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &rules, nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) generateACLPolicySrcIP(u string) (*[]string, error) {
|
func (h *Headscale) generateACLPolicySrcIP(u string) ([]string, error) {
|
||||||
return h.expandAlias(u)
|
return h.expandAlias(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) generateACLPolicyDestPorts(d string) (*[]tailcfg.NetPortRange, error) {
|
func (h *Headscale) generateACLPolicyDestPorts(
|
||||||
|
d string,
|
||||||
|
) ([]tailcfg.NetPortRange, error) {
|
||||||
tokens := strings.Split(d, ":")
|
tokens := strings.Split(d, ":")
|
||||||
if len(tokens) < 2 || len(tokens) > 3 {
|
if len(tokens) < expectedTokenItems || len(tokens) > 3 {
|
||||||
return nil, errorInvalidPortFormat
|
return nil, errInvalidPortFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
var alias string
|
var alias string
|
||||||
@ -112,7 +143,7 @@ func (h *Headscale) generateACLPolicyDestPorts(d string) (*[]tailcfg.NetPortRang
|
|||||||
// tag:montreal-webserver:80,443
|
// tag:montreal-webserver:80,443
|
||||||
// tag:api-server:443
|
// tag:api-server:443
|
||||||
// example-host-1:*
|
// example-host-1:*
|
||||||
if len(tokens) == 2 {
|
if len(tokens) == expectedTokenItems {
|
||||||
alias = tokens[0]
|
alias = tokens[0]
|
||||||
} else {
|
} else {
|
||||||
alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
|
alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
|
||||||
@ -128,7 +159,7 @@ func (h *Headscale) generateACLPolicyDestPorts(d string) (*[]tailcfg.NetPortRang
|
|||||||
}
|
}
|
||||||
|
|
||||||
dests := []tailcfg.NetPortRange{}
|
dests := []tailcfg.NetPortRange{}
|
||||||
for _, d := range *expanded {
|
for _, d := range expanded {
|
||||||
for _, p := range *ports {
|
for _, p := range *ports {
|
||||||
pr := tailcfg.NetPortRange{
|
pr := tailcfg.NetPortRange{
|
||||||
IP: d,
|
IP: d,
|
||||||
@ -137,34 +168,36 @@ func (h *Headscale) generateACLPolicyDestPorts(d string) (*[]tailcfg.NetPortRang
|
|||||||
dests = append(dests, pr)
|
dests = append(dests, pr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &dests, nil
|
|
||||||
|
return dests, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) expandAlias(s string) (*[]string, error) {
|
func (h *Headscale) expandAlias(alias string) ([]string, error) {
|
||||||
if s == "*" {
|
if alias == "*" {
|
||||||
return &[]string{"*"}, nil
|
return []string{"*"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(s, "group:") {
|
if strings.HasPrefix(alias, "group:") {
|
||||||
if _, ok := h.aclPolicy.Groups[s]; !ok {
|
if _, ok := h.aclPolicy.Groups[alias]; !ok {
|
||||||
return nil, errorInvalidGroup
|
return nil, errInvalidGroup
|
||||||
}
|
}
|
||||||
ips := []string{}
|
ips := []string{}
|
||||||
for _, n := range h.aclPolicy.Groups[s] {
|
for _, n := range h.aclPolicy.Groups[alias] {
|
||||||
nodes, err := h.ListMachinesInNamespace(n)
|
nodes, err := h.ListMachinesInNamespace(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorInvalidNamespace
|
return nil, errInvalidNamespace
|
||||||
}
|
}
|
||||||
for _, node := range *nodes {
|
for _, node := range nodes {
|
||||||
ips = append(ips, node.IPAddress)
|
ips = append(ips, node.IPAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ips, nil
|
|
||||||
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(s, "tag:") {
|
if strings.HasPrefix(alias, "tag:") {
|
||||||
if _, ok := h.aclPolicy.TagOwners[s]; !ok {
|
if _, ok := h.aclPolicy.TagOwners[alias]; !ok {
|
||||||
return nil, errorInvalidTag
|
return nil, errInvalidTag
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will have HORRIBLE performance.
|
// This will have HORRIBLE performance.
|
||||||
@ -174,10 +207,10 @@ func (h *Headscale) expandAlias(s string) (*[]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ips := []string{}
|
ips := []string{}
|
||||||
for _, m := range machines {
|
for _, machine := range machines {
|
||||||
hostinfo := tailcfg.Hostinfo{}
|
hostinfo := tailcfg.Hostinfo{}
|
||||||
if len(m.HostInfo) != 0 {
|
if len(machine.HostInfo) != 0 {
|
||||||
hi, err := m.HostInfo.MarshalJSON()
|
hi, err := machine.HostInfo.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -188,69 +221,76 @@ func (h *Headscale) expandAlias(s string) (*[]string, error) {
|
|||||||
|
|
||||||
// FIXME: Check TagOwners allows this
|
// FIXME: Check TagOwners allows this
|
||||||
for _, t := range hostinfo.RequestTags {
|
for _, t := range hostinfo.RequestTags {
|
||||||
if s[4:] == t {
|
if alias[4:] == t {
|
||||||
ips = append(ips, m.IPAddress)
|
ips = append(ips, machine.IPAddress)
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ips, nil
|
|
||||||
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := h.GetNamespace(s)
|
n, err := h.GetNamespace(alias)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
nodes, err := h.ListMachinesInNamespace(n.Name)
|
nodes, err := h.ListMachinesInNamespace(n.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ips := []string{}
|
ips := []string{}
|
||||||
for _, n := range *nodes {
|
for _, n := range nodes {
|
||||||
ips = append(ips, n.IPAddress)
|
ips = append(ips, n.IPAddress)
|
||||||
}
|
}
|
||||||
return &ips, nil
|
|
||||||
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if h, ok := h.aclPolicy.Hosts[s]; ok {
|
if h, ok := h.aclPolicy.Hosts[alias]; ok {
|
||||||
return &[]string{h.String()}, nil
|
return []string{h.String()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := netaddr.ParseIP(s)
|
ip, err := netaddr.ParseIP(alias)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &[]string{ip.String()}, nil
|
return []string{ip.String()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cidr, err := netaddr.ParseIPPrefix(s)
|
cidr, err := netaddr.ParseIPPrefix(alias)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &[]string{cidr.String()}, nil
|
return []string{cidr.String()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errorInvalidUserSection
|
return nil, errInvalidUserSection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) expandPorts(s string) (*[]tailcfg.PortRange, error) {
|
func (h *Headscale) expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
|
||||||
if s == "*" {
|
if portsStr == "*" {
|
||||||
return &[]tailcfg.PortRange{{First: 0, Last: 65535}}, nil
|
return &[]tailcfg.PortRange{
|
||||||
|
{First: portRangeBegin, Last: portRangeEnd},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ports := []tailcfg.PortRange{}
|
ports := []tailcfg.PortRange{}
|
||||||
for _, p := range strings.Split(s, ",") {
|
for _, portStr := range strings.Split(portsStr, ",") {
|
||||||
rang := strings.Split(p, "-")
|
rang := strings.Split(portStr, "-")
|
||||||
if len(rang) == 1 {
|
switch len(rang) {
|
||||||
pi, err := strconv.ParseUint(rang[0], 10, 16)
|
case 1:
|
||||||
|
port, err := strconv.ParseUint(rang[0], Base10, BitSize16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ports = append(ports, tailcfg.PortRange{
|
ports = append(ports, tailcfg.PortRange{
|
||||||
First: uint16(pi),
|
First: uint16(port),
|
||||||
Last: uint16(pi),
|
Last: uint16(port),
|
||||||
})
|
})
|
||||||
} else if len(rang) == 2 {
|
|
||||||
start, err := strconv.ParseUint(rang[0], 10, 16)
|
case expectedTokenItems:
|
||||||
|
start, err := strconv.ParseUint(rang[0], Base10, BitSize16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
last, err := strconv.ParseUint(rang[1], 10, 16)
|
last, err := strconv.ParseUint(rang[1], Base10, BitSize16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -258,9 +298,11 @@ func (h *Headscale) expandPorts(s string) (*[]tailcfg.PortRange, error) {
|
|||||||
First: uint16(start),
|
First: uint16(start),
|
||||||
Last: uint16(last),
|
Last: uint16(last),
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
return nil, errorInvalidPortFormat
|
default:
|
||||||
|
return nil, errInvalidPortFormat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ports, nil
|
return &ports, nil
|
||||||
}
|
}
|
||||||
|
129
acls_test.go
129
acls_test.go
@ -5,156 +5,161 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Suite) TestWrongPath(c *check.C) {
|
func (s *Suite) TestWrongPath(c *check.C) {
|
||||||
err := h.LoadACLPolicy("asdfg")
|
err := app.LoadACLPolicy("asdfg")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestBrokenHuJson(c *check.C) {
|
func (s *Suite) TestBrokenHuJson(c *check.C) {
|
||||||
err := h.LoadACLPolicy("./tests/acls/broken.hujson")
|
err := app.LoadACLPolicy("./tests/acls/broken.hujson")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestInvalidPolicyHuson(c *check.C) {
|
func (s *Suite) TestInvalidPolicyHuson(c *check.C) {
|
||||||
err := h.LoadACLPolicy("./tests/acls/invalid.hujson")
|
err := app.LoadACLPolicy("./tests/acls/invalid.hujson")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
c.Assert(err, check.Equals, errorEmptyPolicy)
|
c.Assert(err, check.Equals, errEmptyPolicy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestParseHosts(c *check.C) {
|
func (s *Suite) TestParseHosts(c *check.C) {
|
||||||
var hs Hosts
|
var hosts Hosts
|
||||||
err := hs.UnmarshalJSON([]byte(`{"example-host-1": "100.100.100.100","example-host-2": "100.100.101.100/24"}`))
|
err := hosts.UnmarshalJSON(
|
||||||
c.Assert(hs, check.NotNil)
|
[]byte(
|
||||||
|
`{"example-host-1": "100.100.100.100","example-host-2": "100.100.101.100/24"}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
c.Assert(hosts, check.NotNil)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestParseInvalidCIDR(c *check.C) {
|
func (s *Suite) TestParseInvalidCIDR(c *check.C) {
|
||||||
var hs Hosts
|
var hosts Hosts
|
||||||
err := hs.UnmarshalJSON([]byte(`{"example-host-1": "100.100.100.100/42"}`))
|
err := hosts.UnmarshalJSON([]byte(`{"example-host-1": "100.100.100.100/42"}`))
|
||||||
c.Assert(hs, check.IsNil)
|
c.Assert(hosts, check.IsNil)
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestRuleInvalidGeneration(c *check.C) {
|
func (s *Suite) TestRuleInvalidGeneration(c *check.C) {
|
||||||
err := h.LoadACLPolicy("./tests/acls/acl_policy_invalid.hujson")
|
err := app.LoadACLPolicy("./tests/acls/acl_policy_invalid.hujson")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestBasicRule(c *check.C) {
|
func (s *Suite) TestBasicRule(c *check.C) {
|
||||||
err := h.LoadACLPolicy("./tests/acls/acl_policy_basic_1.hujson")
|
err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_1.hujson")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
rules, err := h.generateACLRules()
|
rules, err := app.generateACLRules()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(rules, check.NotNil)
|
c.Assert(rules, check.NotNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestPortRange(c *check.C) {
|
func (s *Suite) TestPortRange(c *check.C) {
|
||||||
err := h.LoadACLPolicy("./tests/acls/acl_policy_basic_range.hujson")
|
err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_range.hujson")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
rules, err := h.generateACLRules()
|
rules, err := app.generateACLRules()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(rules, check.NotNil)
|
c.Assert(rules, check.NotNil)
|
||||||
|
|
||||||
c.Assert(*rules, check.HasLen, 1)
|
c.Assert(rules, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].DstPorts, check.HasLen, 1)
|
c.Assert((rules)[0].DstPorts, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(5400))
|
c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(5400))
|
||||||
c.Assert((*rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500))
|
c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(5500))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestPortWildcard(c *check.C) {
|
func (s *Suite) TestPortWildcard(c *check.C) {
|
||||||
err := h.LoadACLPolicy("./tests/acls/acl_policy_basic_wildcards.hujson")
|
err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_wildcards.hujson")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
rules, err := h.generateACLRules()
|
rules, err := app.generateACLRules()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(rules, check.NotNil)
|
c.Assert(rules, check.NotNil)
|
||||||
|
|
||||||
c.Assert(*rules, check.HasLen, 1)
|
c.Assert(rules, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].DstPorts, check.HasLen, 1)
|
c.Assert((rules)[0].DstPorts, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
|
c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
|
||||||
c.Assert((*rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
|
c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
|
||||||
c.Assert((*rules)[0].SrcIPs, check.HasLen, 1)
|
c.Assert((rules)[0].SrcIPs, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].SrcIPs[0], check.Equals, "*")
|
c.Assert((rules)[0].SrcIPs[0], check.Equals, "*")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestPortNamespace(c *check.C) {
|
func (s *Suite) TestPortNamespace(c *check.C) {
|
||||||
n, err := h.CreateNamespace("testnamespace")
|
namespace, err := app.CreateNamespace("testnamespace")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak, err := h.CreatePreAuthKey(n.Name, false, false, nil)
|
pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
_, err = h.GetMachine("testnamespace", "testmachine")
|
_, err = app.GetMachine("testnamespace", "testmachine")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
ip, _ := h.getAvailableIP()
|
ip, _ := app.getAvailableIP()
|
||||||
m := Machine{
|
machine := Machine{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
MachineKey: "foo",
|
MachineKey: "foo",
|
||||||
NodeKey: "bar",
|
NodeKey: "bar",
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: n.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: "authKey",
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: ip.String(),
|
IPAddress: ip.String(),
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
h.db.Save(&m)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
err = h.LoadACLPolicy("./tests/acls/acl_policy_basic_namespace_as_user.hujson")
|
err = app.LoadACLPolicy(
|
||||||
|
"./tests/acls/acl_policy_basic_namespace_as_user.hujson",
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
rules, err := h.generateACLRules()
|
rules, err := app.generateACLRules()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(rules, check.NotNil)
|
c.Assert(rules, check.NotNil)
|
||||||
|
|
||||||
c.Assert(*rules, check.HasLen, 1)
|
c.Assert(rules, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].DstPorts, check.HasLen, 1)
|
c.Assert((rules)[0].DstPorts, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
|
c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
|
||||||
c.Assert((*rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
|
c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
|
||||||
c.Assert((*rules)[0].SrcIPs, check.HasLen, 1)
|
c.Assert((rules)[0].SrcIPs, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].SrcIPs[0], check.Not(check.Equals), "not an ip")
|
c.Assert((rules)[0].SrcIPs[0], check.Not(check.Equals), "not an ip")
|
||||||
c.Assert((*rules)[0].SrcIPs[0], check.Equals, ip.String())
|
c.Assert((rules)[0].SrcIPs[0], check.Equals, ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestPortGroup(c *check.C) {
|
func (s *Suite) TestPortGroup(c *check.C) {
|
||||||
n, err := h.CreateNamespace("testnamespace")
|
namespace, err := app.CreateNamespace("testnamespace")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak, err := h.CreatePreAuthKey(n.Name, false, false, nil)
|
pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
_, err = h.GetMachine("testnamespace", "testmachine")
|
_, err = app.GetMachine("testnamespace", "testmachine")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
ip, _ := h.getAvailableIP()
|
ip, _ := app.getAvailableIP()
|
||||||
m := Machine{
|
machine := Machine{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
MachineKey: "foo",
|
MachineKey: "foo",
|
||||||
NodeKey: "bar",
|
NodeKey: "bar",
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: n.ID,
|
NamespaceID: namespace.ID,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: "authKey",
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: ip.String(),
|
IPAddress: ip.String(),
|
||||||
AuthKeyID: uint(pak.ID),
|
AuthKeyID: uint(pak.ID),
|
||||||
}
|
}
|
||||||
h.db.Save(&m)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
err = h.LoadACLPolicy("./tests/acls/acl_policy_basic_groups.hujson")
|
err = app.LoadACLPolicy("./tests/acls/acl_policy_basic_groups.hujson")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
rules, err := h.generateACLRules()
|
rules, err := app.generateACLRules()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(rules, check.NotNil)
|
c.Assert(rules, check.NotNil)
|
||||||
|
|
||||||
c.Assert(*rules, check.HasLen, 1)
|
c.Assert(rules, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].DstPorts, check.HasLen, 1)
|
c.Assert((rules)[0].DstPorts, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
|
c.Assert((rules)[0].DstPorts[0].Ports.First, check.Equals, uint16(0))
|
||||||
c.Assert((*rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
|
c.Assert((rules)[0].DstPorts[0].Ports.Last, check.Equals, uint16(65535))
|
||||||
c.Assert((*rules)[0].SrcIPs, check.HasLen, 1)
|
c.Assert((rules)[0].SrcIPs, check.HasLen, 1)
|
||||||
c.Assert((*rules)[0].SrcIPs[0], check.Not(check.Equals), "not an ip")
|
c.Assert((rules)[0].SrcIPs[0], check.Not(check.Equals), "not an ip")
|
||||||
c.Assert((*rules)[0].SrcIPs[0], check.Equals, ip.String())
|
c.Assert((rules)[0].SrcIPs[0], check.Equals, ip.String())
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tailscale/hujson"
|
"github.com/tailscale/hujson"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ACLPolicy represents a Tailscale ACL Policy
|
// ACLPolicy represents a Tailscale ACL Policy.
|
||||||
type ACLPolicy struct {
|
type ACLPolicy struct {
|
||||||
Groups Groups `json:"Groups"`
|
Groups Groups `json:"Groups"`
|
||||||
Hosts Hosts `json:"Hosts"`
|
Hosts Hosts `json:"Hosts"`
|
||||||
@ -16,55 +17,63 @@ type ACLPolicy struct {
|
|||||||
Tests []ACLTest `json:"Tests"`
|
Tests []ACLTest `json:"Tests"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ACL is a basic rule for the ACL Policy
|
// ACL is a basic rule for the ACL Policy.
|
||||||
type ACL struct {
|
type ACL struct {
|
||||||
Action string `json:"Action"`
|
Action string `json:"Action"`
|
||||||
Users []string `json:"Users"`
|
Users []string `json:"Users"`
|
||||||
Ports []string `json:"Ports"`
|
Ports []string `json:"Ports"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Groups references a series of alias in the ACL rules
|
// Groups references a series of alias in the ACL rules.
|
||||||
type Groups map[string][]string
|
type Groups map[string][]string
|
||||||
|
|
||||||
// Hosts are alias for IP addresses or subnets
|
// Hosts are alias for IP addresses or subnets.
|
||||||
type Hosts map[string]netaddr.IPPrefix
|
type Hosts map[string]netaddr.IPPrefix
|
||||||
|
|
||||||
// TagOwners specify what users (namespaces?) are allow to use certain tags
|
// TagOwners specify what users (namespaces?) are allow to use certain tags.
|
||||||
type TagOwners map[string][]string
|
type TagOwners map[string][]string
|
||||||
|
|
||||||
// ACLTest is not implemented, but should be use to check if a certain rule is allowed
|
// ACLTest is not implemented, but should be use to check if a certain rule is allowed.
|
||||||
type ACLTest struct {
|
type ACLTest struct {
|
||||||
User string `json:"User"`
|
User string `json:"User"`
|
||||||
Allow []string `json:"Allow"`
|
Allow []string `json:"Allow"`
|
||||||
Deny []string `json:"Deny,omitempty"`
|
Deny []string `json:"Deny,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON allows to parse the Hosts directly into netaddr objects
|
// UnmarshalJSON allows to parse the Hosts directly into netaddr objects.
|
||||||
func (h *Hosts) UnmarshalJSON(data []byte) error {
|
func (hosts *Hosts) UnmarshalJSON(data []byte) error {
|
||||||
hosts := Hosts{}
|
newHosts := Hosts{}
|
||||||
hs := make(map[string]string)
|
hostIPPrefixMap := make(map[string]string)
|
||||||
err := hujson.Unmarshal(data, &hs)
|
ast, err := hujson.Parse(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for k, v := range hs {
|
ast.Standardize()
|
||||||
if !strings.Contains(v, "/") {
|
data = ast.Pack()
|
||||||
v = v + "/32"
|
err = json.Unmarshal(data, &hostIPPrefixMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for host, prefixStr := range hostIPPrefixMap {
|
||||||
|
if !strings.Contains(prefixStr, "/") {
|
||||||
|
prefixStr += "/32"
|
||||||
}
|
}
|
||||||
prefix, err := netaddr.ParseIPPrefix(v)
|
prefix, err := netaddr.ParseIPPrefix(prefixStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
hosts[k] = prefix
|
newHosts[host] = prefix
|
||||||
}
|
}
|
||||||
*h = hosts
|
*hosts = newHosts
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsZero is perhaps a bit naive here
|
// IsZero is perhaps a bit naive here.
|
||||||
func (p ACLPolicy) IsZero() bool {
|
func (policy ACLPolicy) IsZero() bool {
|
||||||
if len(p.Groups) == 0 && len(p.Hosts) == 0 && len(p.ACLs) == 0 {
|
if len(policy.Groups) == 0 && len(policy.Hosts) == 0 && len(policy.ACLs) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
644
api.go
644
api.go
@ -1,40 +1,51 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/wgkey"
|
"tailscale.com/types/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
reservedResponseHeaderSize = 4
|
||||||
|
RegisterMethodAuthKey = "authKey"
|
||||||
|
RegisterMethodOIDC = "oidc"
|
||||||
|
RegisterMethodCLI = "cli"
|
||||||
|
ErrRegisterMethodCLIDoesNotSupportExpire = Error(
|
||||||
|
"machines registered with CLI does not support expire",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// KeyHandler provides the Headscale pub key
|
// KeyHandler provides the Headscale pub key
|
||||||
// Listens in /key
|
// Listens in /key.
|
||||||
func (h *Headscale) KeyHandler(c *gin.Context) {
|
func (h *Headscale) KeyHandler(ctx *gin.Context) {
|
||||||
c.Data(200, "text/plain; charset=utf-8", []byte(h.publicKey.HexString()))
|
ctx.Data(
|
||||||
|
http.StatusOK,
|
||||||
|
"text/plain; charset=utf-8",
|
||||||
|
[]byte(MachinePublicKeyStripPrefix(h.privateKey.Public())),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterWebAPI shows a simple message in the browser to point to the CLI
|
type registerWebAPITemplateConfig struct {
|
||||||
// Listens in /register
|
Key string
|
||||||
func (h *Headscale) RegisterWebAPI(c *gin.Context) {
|
}
|
||||||
mKeyStr := c.Query("key")
|
|
||||||
if mKeyStr == "" {
|
|
||||||
c.String(http.StatusBadRequest, "Wrong params")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(fmt.Sprintf(`
|
var registerWebAPITemplate = template.Must(
|
||||||
<html>
|
template.New("registerweb").Parse(`<html>
|
||||||
<body>
|
<body>
|
||||||
<h1>headscale</h1>
|
<h1>headscale</h1>
|
||||||
<p>
|
<p>
|
||||||
@ -43,228 +54,193 @@ func (h *Headscale) RegisterWebAPI(c *gin.Context) {
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
<code>
|
<code>
|
||||||
<b>headscale -n NAMESPACE nodes register %s</b>
|
<b>headscale -n NAMESPACE nodes register --key {{.Key}}</b>
|
||||||
</code>
|
</code>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>`),
|
||||||
|
)
|
||||||
|
|
||||||
`, mKeyStr)))
|
// RegisterWebAPI shows a simple message in the browser to point to the CLI
|
||||||
|
// Listens in /register.
|
||||||
|
func (h *Headscale) RegisterWebAPI(ctx *gin.Context) {
|
||||||
|
machineKeyStr := ctx.Query("key")
|
||||||
|
if machineKeyStr == "" {
|
||||||
|
ctx.String(http.StatusBadRequest, "Wrong params")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var content bytes.Buffer
|
||||||
|
if err := registerWebAPITemplate.Execute(&content, registerWebAPITemplateConfig{
|
||||||
|
Key: machineKeyStr,
|
||||||
|
}); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Str("func", "RegisterWebAPI").
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not render register web API template")
|
||||||
|
ctx.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render register web API template"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data(http.StatusOK, "text/html; charset=utf-8", content.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegistrationHandler handles the actual registration process of a machine
|
// RegistrationHandler handles the actual registration process of a machine
|
||||||
// Endpoint /machine/:id
|
// Endpoint /machine/:id.
|
||||||
func (h *Headscale) RegistrationHandler(c *gin.Context) {
|
func (h *Headscale) RegistrationHandler(ctx *gin.Context) {
|
||||||
body, _ := io.ReadAll(c.Request.Body)
|
body, _ := io.ReadAll(ctx.Request.Body)
|
||||||
mKeyStr := c.Param("id")
|
machineKeyStr := ctx.Param("id")
|
||||||
mKey, err := wgkey.ParseHex(mKeyStr)
|
|
||||||
|
var machineKey key.MachinePublic
|
||||||
|
err := machineKey.UnmarshalText([]byte(MachinePublicKeyEnsurePrefix(machineKeyStr)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("handler", "Registration").
|
Caller().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot parse machine key")
|
Msg("Cannot parse machine key")
|
||||||
machineRegistrations.WithLabelValues("unkown", "web", "error", "unknown").Inc()
|
machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||||
c.String(http.StatusInternalServerError, "Sad!")
|
ctx.String(http.StatusInternalServerError, "Sad!")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req := tailcfg.RegisterRequest{}
|
req := tailcfg.RegisterRequest{}
|
||||||
err = decode(body, &req, &mKey, h.privateKey)
|
err = decode(body, &req, &machineKey, h.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("handler", "Registration").
|
Caller().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot decode message")
|
Msg("Cannot decode message")
|
||||||
machineRegistrations.WithLabelValues("unkown", "web", "error", "unknown").Inc()
|
machineRegistrations.WithLabelValues("unknown", "web", "error", "unknown").Inc()
|
||||||
c.String(http.StatusInternalServerError, "Very sad!")
|
ctx.String(http.StatusInternalServerError, "Very sad!")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
var m Machine
|
machine, err := h.GetMachineByMachineKey(machineKey)
|
||||||
if result := h.db.Preload("Namespace").First(&m, "machine_key = ?", mKey.HexString()); errors.Is(
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
result.Error,
|
|
||||||
gorm.ErrRecordNotFound,
|
|
||||||
) {
|
|
||||||
log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine")
|
log.Info().Str("machine", req.Hostinfo.Hostname).Msg("New machine")
|
||||||
m = Machine{
|
newMachine := Machine{
|
||||||
Expiry: &req.Expiry,
|
Expiry: &time.Time{},
|
||||||
MachineKey: mKey.HexString(),
|
MachineKey: MachinePublicKeyStripPrefix(machineKey),
|
||||||
Name: req.Hostinfo.Hostname,
|
Name: req.Hostinfo.Hostname,
|
||||||
NodeKey: wgkey.Key(req.NodeKey).HexString(),
|
|
||||||
LastSuccessfulUpdate: &now,
|
|
||||||
}
|
}
|
||||||
if err := h.db.Create(&m).Error; err != nil {
|
if err := h.db.Create(&newMachine).Error; err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("handler", "Registration").
|
Caller().
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not create row")
|
Msg("Could not create row")
|
||||||
machineRegistrations.WithLabelValues("unkown", "web", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("unknown", "web", "error", machine.Namespace.Name).
|
||||||
|
Inc()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
machine = &newMachine
|
||||||
}
|
}
|
||||||
|
|
||||||
if !m.Registered && req.Auth.AuthKey != "" {
|
if machine.Registered {
|
||||||
h.handleAuthKey(c, h.db, mKey, req, m)
|
// If the NodeKey stored in headscale is the same as the key presented in a registration
|
||||||
return
|
// request, then we have a node that is either:
|
||||||
}
|
// - Trying to log out (sending a expiry in the past)
|
||||||
|
// - A valid, registered machine, looking for the node map
|
||||||
|
// - Expired machine wanting to reauthenticate
|
||||||
|
if machine.NodeKey == NodePublicKeyStripPrefix(req.NodeKey) {
|
||||||
|
// The client sends an Expiry in the past if the client is requesting to expire the key (aka logout)
|
||||||
|
// https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L648
|
||||||
|
if !req.Expiry.IsZero() && req.Expiry.UTC().Before(now) {
|
||||||
|
h.handleMachineLogOut(ctx, machineKey, *machine)
|
||||||
|
|
||||||
resp := tailcfg.RegisterResponse{}
|
|
||||||
|
|
||||||
// We have the updated key!
|
|
||||||
if m.NodeKey == wgkey.Key(req.NodeKey).HexString() {
|
|
||||||
if m.Registered {
|
|
||||||
log.Debug().
|
|
||||||
Str("handler", "Registration").
|
|
||||||
Str("machine", m.Name).
|
|
||||||
Msg("Client is registered and we have the current NodeKey. All clear to /map")
|
|
||||||
|
|
||||||
resp.AuthURL = ""
|
|
||||||
resp.MachineAuthorized = true
|
|
||||||
resp.User = *m.Namespace.toUser()
|
|
||||||
respBody, err := encode(resp, &mKey, h.privateKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("handler", "Registration").
|
|
||||||
Err(err).
|
|
||||||
Msg("Cannot encode message")
|
|
||||||
machineRegistrations.WithLabelValues("update", "web", "error", m.Namespace.Name).Inc()
|
|
||||||
c.String(http.StatusInternalServerError, "")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
machineRegistrations.WithLabelValues("update", "web", "success", m.Namespace.Name).Inc()
|
|
||||||
c.Data(200, "application/json; charset=utf-8", respBody)
|
// If machine is not expired, and is register, we have a already accepted this machine,
|
||||||
|
// let it proceed with a valid registration
|
||||||
|
if !machine.isExpired() {
|
||||||
|
h.handleMachineValidRegistration(ctx, machineKey, *machine)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The NodeKey we have matches OldNodeKey, which means this is a refresh after a key expiration
|
||||||
|
if machine.NodeKey == NodePublicKeyStripPrefix(req.OldNodeKey) &&
|
||||||
|
!machine.isExpired() {
|
||||||
|
h.handleMachineRefreshKey(ctx, machineKey, req, *machine)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().
|
// The machine has expired
|
||||||
Str("handler", "Registration").
|
h.handleMachineExpired(ctx, machineKey, req, *machine)
|
||||||
Str("machine", m.Name).
|
|
||||||
Msg("Not registered and not NodeKey rotation. Sending a authurl to register")
|
|
||||||
resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
|
|
||||||
h.cfg.ServerURL, mKey.HexString())
|
|
||||||
respBody, err := encode(resp, &mKey, h.privateKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("handler", "Registration").
|
|
||||||
Err(err).
|
|
||||||
Msg("Cannot encode message")
|
|
||||||
machineRegistrations.WithLabelValues("new", "web", "error", m.Namespace.Name).Inc()
|
|
||||||
c.String(http.StatusInternalServerError, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
machineRegistrations.WithLabelValues("new", "web", "success", m.Namespace.Name).Inc()
|
|
||||||
c.Data(200, "application/json; charset=utf-8", respBody)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// The NodeKey we have matches OldNodeKey, which means this is a refresh after an key expiration
|
// If the machine has AuthKey set, handle registration via PreAuthKeys
|
||||||
if m.NodeKey == wgkey.Key(req.OldNodeKey).HexString() {
|
if req.Auth.AuthKey != "" {
|
||||||
log.Debug().
|
h.handleAuthKey(ctx, machineKey, req, *machine)
|
||||||
Str("handler", "Registration").
|
|
||||||
Str("machine", m.Name).
|
|
||||||
Msg("We have the OldNodeKey in the database. This is a key refresh")
|
|
||||||
m.NodeKey = wgkey.Key(req.NodeKey).HexString()
|
|
||||||
h.db.Save(&m)
|
|
||||||
|
|
||||||
resp.AuthURL = ""
|
|
||||||
resp.User = *m.Namespace.toUser()
|
|
||||||
respBody, err := encode(resp, &mKey, h.privateKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("handler", "Registration").
|
|
||||||
Err(err).
|
|
||||||
Msg("Cannot encode message")
|
|
||||||
c.String(http.StatusInternalServerError, "Extremely sad!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Data(200, "application/json; charset=utf-8", respBody)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// We arrive here after a client is restarted without finalizing the authentication flow or
|
h.handleMachineRegistrationNew(ctx, machineKey, req, *machine)
|
||||||
// when headscale is stopped in the middle of the auth process.
|
|
||||||
if m.Registered {
|
|
||||||
log.Debug().
|
|
||||||
Str("handler", "Registration").
|
|
||||||
Str("machine", m.Name).
|
|
||||||
Msg("The node is sending us a new NodeKey, but machine is registered. All clear for /map")
|
|
||||||
resp.AuthURL = ""
|
|
||||||
resp.MachineAuthorized = true
|
|
||||||
resp.User = *m.Namespace.toUser()
|
|
||||||
respBody, err := encode(resp, &mKey, h.privateKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("handler", "Registration").
|
|
||||||
Err(err).
|
|
||||||
Msg("Cannot encode message")
|
|
||||||
c.String(http.StatusInternalServerError, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Data(200, "application/json; charset=utf-8", respBody)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().
|
|
||||||
Str("handler", "Registration").
|
|
||||||
Str("machine", m.Name).
|
|
||||||
Msg("The node is sending us a new NodeKey, sending auth url")
|
|
||||||
resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
|
|
||||||
h.cfg.ServerURL, mKey.HexString())
|
|
||||||
respBody, err := encode(resp, &mKey, h.privateKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
|
||||||
Str("handler", "Registration").
|
|
||||||
Err(err).
|
|
||||||
Msg("Cannot encode message")
|
|
||||||
c.String(http.StatusInternalServerError, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Data(200, "application/json; charset=utf-8", respBody)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Machine) ([]byte, error) {
|
func (h *Headscale) getMapResponse(
|
||||||
|
machineKey key.MachinePublic,
|
||||||
|
req tailcfg.MapRequest,
|
||||||
|
machine *Machine,
|
||||||
|
) ([]byte, error) {
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Str("func", "getMapResponse").
|
Str("func", "getMapResponse").
|
||||||
Str("machine", req.Hostinfo.Hostname).
|
Str("machine", req.Hostinfo.Hostname).
|
||||||
Msg("Creating Map response")
|
Msg("Creating Map response")
|
||||||
node, err := m.toNode(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
|
node, err := machine.toNode(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
|
Caller().
|
||||||
Str("func", "getMapResponse").
|
Str("func", "getMapResponse").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot convert to node")
|
Msg("Cannot convert to node")
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
peers, err := h.getPeers(m)
|
peers, err := h.getValidPeers(machine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
|
Caller().
|
||||||
Str("func", "getMapResponse").
|
Str("func", "getMapResponse").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot fetch peers")
|
Msg("Cannot fetch peers")
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles := getMapResponseUserProfiles(*m, peers)
|
profiles := getMapResponseUserProfiles(*machine, peers)
|
||||||
|
|
||||||
nodePeers, err := peers.toNodes(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
|
nodePeers, err := peers.toNodes(h.cfg.BaseDomain, h.cfg.DNSConfig, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
|
Caller().
|
||||||
Str("func", "getMapResponse").
|
Str("func", "getMapResponse").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Failed to convert peers to Tailscale nodes")
|
Msg("Failed to convert peers to Tailscale nodes")
|
||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsConfig, err := getMapResponseDNSConfig(h.cfg.DNSConfig, h.cfg.BaseDomain, *m, peers)
|
dnsConfig := getMapResponseDNSConfig(
|
||||||
if err != nil {
|
h.cfg.DNSConfig,
|
||||||
log.Error().
|
h.cfg.BaseDomain,
|
||||||
Str("func", "getMapResponse").
|
*machine,
|
||||||
Err(err).
|
peers,
|
||||||
Msg("Failed generate the DNSConfig")
|
)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := tailcfg.MapResponse{
|
resp := tailcfg.MapResponse{
|
||||||
KeepAlive: false,
|
KeepAlive: false,
|
||||||
@ -272,7 +248,7 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Ma
|
|||||||
Peers: nodePeers,
|
Peers: nodePeers,
|
||||||
DNSConfig: dnsConfig,
|
DNSConfig: dnsConfig,
|
||||||
Domain: h.cfg.BaseDomain,
|
Domain: h.cfg.BaseDomain,
|
||||||
PacketFilter: *h.aclRules,
|
PacketFilter: h.aclRules,
|
||||||
DERPMap: h.DERPMap,
|
DERPMap: h.DERPMap,
|
||||||
UserProfiles: profiles,
|
UserProfiles: profiles,
|
||||||
}
|
}
|
||||||
@ -289,137 +265,351 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Ma
|
|||||||
|
|
||||||
encoder, _ := zstd.NewWriter(nil)
|
encoder, _ := zstd.NewWriter(nil)
|
||||||
srcCompressed := encoder.EncodeAll(src, nil)
|
srcCompressed := encoder.EncodeAll(src, nil)
|
||||||
respBody, err = encodeMsg(srcCompressed, &mKey, h.privateKey)
|
respBody = h.privateKey.SealTo(machineKey, srcCompressed)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
respBody, err = encode(resp, &mKey, h.privateKey)
|
respBody, err = encode(resp, &machineKey, h.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// declare the incoming size on the first 4 bytes
|
// declare the incoming size on the first 4 bytes
|
||||||
data := make([]byte, 4)
|
data := make([]byte, reservedResponseHeaderSize)
|
||||||
binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
|
binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
|
||||||
data = append(data, respBody...)
|
data = append(data, respBody...)
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) getMapKeepAliveResponse(mKey wgkey.Key, req tailcfg.MapRequest, m *Machine) ([]byte, error) {
|
func (h *Headscale) getMapKeepAliveResponse(
|
||||||
resp := tailcfg.MapResponse{
|
machineKey key.MachinePublic,
|
||||||
|
mapRequest tailcfg.MapRequest,
|
||||||
|
) ([]byte, error) {
|
||||||
|
mapResponse := tailcfg.MapResponse{
|
||||||
KeepAlive: true,
|
KeepAlive: true,
|
||||||
}
|
}
|
||||||
var respBody []byte
|
var respBody []byte
|
||||||
var err error
|
var err error
|
||||||
if req.Compress == "zstd" {
|
if mapRequest.Compress == "zstd" {
|
||||||
src, _ := json.Marshal(resp)
|
src, _ := json.Marshal(mapResponse)
|
||||||
encoder, _ := zstd.NewWriter(nil)
|
encoder, _ := zstd.NewWriter(nil)
|
||||||
srcCompressed := encoder.EncodeAll(src, nil)
|
srcCompressed := encoder.EncodeAll(src, nil)
|
||||||
respBody, err = encodeMsg(srcCompressed, &mKey, h.privateKey)
|
respBody = h.privateKey.SealTo(machineKey, srcCompressed)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
respBody, err = encode(resp, &mKey, h.privateKey)
|
respBody, err = encode(mapResponse, &machineKey, h.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data := make([]byte, 4)
|
data := make([]byte, reservedResponseHeaderSize)
|
||||||
binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
|
binary.LittleEndian.PutUint32(data, uint32(len(respBody)))
|
||||||
data = append(data, respBody...)
|
data = append(data, respBody...)
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) handleMachineLogOut(
|
||||||
|
ctx *gin.Context,
|
||||||
|
machineKey key.MachinePublic,
|
||||||
|
machine Machine,
|
||||||
|
) {
|
||||||
|
resp := tailcfg.RegisterResponse{}
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Msg("Client requested logout")
|
||||||
|
|
||||||
|
h.ExpireMachine(&machine)
|
||||||
|
|
||||||
|
resp.AuthURL = ""
|
||||||
|
resp.MachineAuthorized = false
|
||||||
|
resp.User = *machine.Namespace.toUser()
|
||||||
|
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Err(err).
|
||||||
|
Msg("Cannot encode message")
|
||||||
|
ctx.String(http.StatusInternalServerError, "")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) handleMachineValidRegistration(
|
||||||
|
ctx *gin.Context,
|
||||||
|
machineKey key.MachinePublic,
|
||||||
|
machine Machine,
|
||||||
|
) {
|
||||||
|
resp := tailcfg.RegisterResponse{}
|
||||||
|
|
||||||
|
// The machine registration is valid, respond with redirect to /map
|
||||||
|
log.Debug().
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Msg("Client is registered and we have the current NodeKey. All clear to /map")
|
||||||
|
|
||||||
|
resp.AuthURL = ""
|
||||||
|
resp.MachineAuthorized = true
|
||||||
|
resp.User = *machine.Namespace.toUser()
|
||||||
|
resp.Login = *machine.Namespace.toLogin()
|
||||||
|
|
||||||
|
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Err(err).
|
||||||
|
Msg("Cannot encode message")
|
||||||
|
machineRegistrations.WithLabelValues("update", "web", "error", machine.Namespace.Name).
|
||||||
|
Inc()
|
||||||
|
ctx.String(http.StatusInternalServerError, "")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
machineRegistrations.WithLabelValues("update", "web", "success", machine.Namespace.Name).
|
||||||
|
Inc()
|
||||||
|
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) handleMachineExpired(
|
||||||
|
ctx *gin.Context,
|
||||||
|
machineKey key.MachinePublic,
|
||||||
|
registerRequest tailcfg.RegisterRequest,
|
||||||
|
machine Machine,
|
||||||
|
) {
|
||||||
|
resp := tailcfg.RegisterResponse{}
|
||||||
|
|
||||||
|
// The client has registered before, but has expired
|
||||||
|
log.Debug().
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Msg("Machine registration has expired. Sending a authurl to register")
|
||||||
|
|
||||||
|
if registerRequest.Auth.AuthKey != "" {
|
||||||
|
h.handleAuthKey(ctx, machineKey, registerRequest, machine)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.cfg.OIDC.Issuer != "" {
|
||||||
|
resp.AuthURL = fmt.Sprintf("%s/oidc/register/%s",
|
||||||
|
strings.TrimSuffix(h.cfg.ServerURL, "/"), machineKey.String())
|
||||||
|
} else {
|
||||||
|
resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
|
||||||
|
strings.TrimSuffix(h.cfg.ServerURL, "/"), machineKey.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Err(err).
|
||||||
|
Msg("Cannot encode message")
|
||||||
|
machineRegistrations.WithLabelValues("reauth", "web", "error", machine.Namespace.Name).
|
||||||
|
Inc()
|
||||||
|
ctx.String(http.StatusInternalServerError, "")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
machineRegistrations.WithLabelValues("reauth", "web", "success", machine.Namespace.Name).
|
||||||
|
Inc()
|
||||||
|
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) handleMachineRefreshKey(
|
||||||
|
ctx *gin.Context,
|
||||||
|
machineKey key.MachinePublic,
|
||||||
|
registerRequest tailcfg.RegisterRequest,
|
||||||
|
machine Machine,
|
||||||
|
) {
|
||||||
|
resp := tailcfg.RegisterResponse{}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Msg("We have the OldNodeKey in the database. This is a key refresh")
|
||||||
|
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||||
|
h.db.Save(&machine)
|
||||||
|
|
||||||
|
resp.AuthURL = ""
|
||||||
|
resp.User = *machine.Namespace.toUser()
|
||||||
|
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Err(err).
|
||||||
|
Msg("Cannot encode message")
|
||||||
|
ctx.String(http.StatusInternalServerError, "Extremely sad!")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) handleMachineRegistrationNew(
|
||||||
|
ctx *gin.Context,
|
||||||
|
machineKey key.MachinePublic,
|
||||||
|
registerRequest tailcfg.RegisterRequest,
|
||||||
|
machine Machine,
|
||||||
|
) {
|
||||||
|
resp := tailcfg.RegisterResponse{}
|
||||||
|
|
||||||
|
// The machine registration is new, redirect the client to the registration URL
|
||||||
|
log.Debug().
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Msg("The node is sending us a new NodeKey, sending auth url")
|
||||||
|
if h.cfg.OIDC.Issuer != "" {
|
||||||
|
resp.AuthURL = fmt.Sprintf(
|
||||||
|
"%s/oidc/register/%s",
|
||||||
|
strings.TrimSuffix(h.cfg.ServerURL, "/"),
|
||||||
|
machineKey.String(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
resp.AuthURL = fmt.Sprintf("%s/register?key=%s",
|
||||||
|
strings.TrimSuffix(h.cfg.ServerURL, "/"), MachinePublicKeyStripPrefix(machineKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !registerRequest.Expiry.IsZero() {
|
||||||
|
log.Trace().
|
||||||
|
Caller().
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Time("expiry", registerRequest.Expiry).
|
||||||
|
Msg("Non-zero expiry time requested, adding to cache")
|
||||||
|
h.requestedExpiryCache.Set(
|
||||||
|
machineKey.String(),
|
||||||
|
registerRequest.Expiry,
|
||||||
|
requestedExpiryCacheExpiration,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||||
|
|
||||||
|
// save the NodeKey
|
||||||
|
h.db.Save(&machine)
|
||||||
|
|
||||||
|
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Err(err).
|
||||||
|
Msg("Cannot encode message")
|
||||||
|
ctx.String(http.StatusInternalServerError, "")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Headscale) handleAuthKey(
|
func (h *Headscale) handleAuthKey(
|
||||||
c *gin.Context,
|
ctx *gin.Context,
|
||||||
db *gorm.DB,
|
machineKey key.MachinePublic,
|
||||||
idKey wgkey.Key,
|
registerRequest tailcfg.RegisterRequest,
|
||||||
req tailcfg.RegisterRequest,
|
machine Machine,
|
||||||
m Machine,
|
|
||||||
) {
|
) {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", req.Hostinfo.Hostname).
|
Str("machine", registerRequest.Hostinfo.Hostname).
|
||||||
Msgf("Processing auth key for %s", req.Hostinfo.Hostname)
|
Msgf("Processing auth key for %s", registerRequest.Hostinfo.Hostname)
|
||||||
resp := tailcfg.RegisterResponse{}
|
resp := tailcfg.RegisterResponse{}
|
||||||
pak, err := h.checkKeyValidity(req.Auth.AuthKey)
|
pak, err := h.checkKeyValidity(registerRequest.Auth.AuthKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", m.Name).
|
Str("machine", machine.Name).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Failed authentication via AuthKey")
|
Msg("Failed authentication via AuthKey")
|
||||||
resp.MachineAuthorized = false
|
resp.MachineAuthorized = false
|
||||||
respBody, err := encode(resp, &idKey, h.privateKey)
|
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", m.Name).
|
Str("machine", machine.Name).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot encode message")
|
Msg("Cannot encode message")
|
||||||
c.String(http.StatusInternalServerError, "")
|
ctx.String(http.StatusInternalServerError, "")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
|
||||||
|
Inc()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Data(401, "application/json; charset=utf-8", respBody)
|
ctx.Data(http.StatusUnauthorized, "application/json; charset=utf-8", respBody)
|
||||||
log.Error().
|
log.Error().
|
||||||
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", m.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("Failed authentication via AuthKey")
|
Msg("Failed authentication via AuthKey")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
|
||||||
|
Inc()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().
|
if machine.isRegistered() {
|
||||||
Str("func", "handleAuthKey").
|
log.Trace().
|
||||||
Str("machine", m.Name).
|
Caller().
|
||||||
Msg("Authentication key was valid, proceeding to acquire an IP address")
|
Str("machine", machine.Name).
|
||||||
ip, err := h.getAvailableIP()
|
Msg("machine already registered, reauthenticating")
|
||||||
if err != nil {
|
|
||||||
log.Error().
|
h.RefreshMachine(&machine, registerRequest.Expiry)
|
||||||
|
} else {
|
||||||
|
log.Debug().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", m.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("Failed to find an available IP")
|
Msg("Authentication key was valid, proceeding to acquire an IP address")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
|
ip, err := h.getAvailableIP()
|
||||||
return
|
if err != nil {
|
||||||
}
|
log.Error().
|
||||||
log.Info().
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", m.Name).
|
Str("machine", machine.Name).
|
||||||
Str("ip", ip.String()).
|
Msg("Failed to find an available IP")
|
||||||
Msgf("Assigning %s to %s", ip, m.Name)
|
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
|
||||||
|
Inc()
|
||||||
|
|
||||||
m.AuthKeyID = uint(pak.ID)
|
return
|
||||||
m.IPAddress = ip.String()
|
}
|
||||||
m.NamespaceID = pak.NamespaceID
|
log.Info().
|
||||||
m.NodeKey = wgkey.Key(req.NodeKey).HexString() // we update it just in case
|
Str("func", "handleAuthKey").
|
||||||
m.Registered = true
|
Str("machine", machine.Name).
|
||||||
m.RegisterMethod = "authKey"
|
Str("ip", ip.String()).
|
||||||
db.Save(&m)
|
Msgf("Assigning %s to %s", ip, machine.Name)
|
||||||
|
|
||||||
|
machine.Expiry = ®isterRequest.Expiry
|
||||||
|
machine.AuthKeyID = uint(pak.ID)
|
||||||
|
machine.IPAddress = ip.String()
|
||||||
|
machine.NamespaceID = pak.NamespaceID
|
||||||
|
|
||||||
|
machine.NodeKey = NodePublicKeyStripPrefix(registerRequest.NodeKey)
|
||||||
|
// we update it just in case
|
||||||
|
machine.Registered = true
|
||||||
|
machine.RegisterMethod = RegisterMethodAuthKey
|
||||||
|
h.db.Save(&machine)
|
||||||
|
}
|
||||||
|
|
||||||
pak.Used = true
|
pak.Used = true
|
||||||
db.Save(&pak)
|
h.db.Save(&pak)
|
||||||
|
|
||||||
resp.MachineAuthorized = true
|
resp.MachineAuthorized = true
|
||||||
resp.User = *pak.Namespace.toUser()
|
resp.User = *pak.Namespace.toUser()
|
||||||
respBody, err := encode(resp, &idKey, h.privateKey)
|
respBody, err := encode(resp, &machineKey, h.privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
|
Caller().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", m.Name).
|
Str("machine", machine.Name).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Cannot encode message")
|
Msg("Cannot encode message")
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "error", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "authkey", "error", machine.Namespace.Name).
|
||||||
c.String(http.StatusInternalServerError, "Extremely sad!")
|
Inc()
|
||||||
|
ctx.String(http.StatusInternalServerError, "Extremely sad!")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
machineRegistrations.WithLabelValues("new", "authkey", "success", m.Namespace.Name).Inc()
|
machineRegistrations.WithLabelValues("new", "authkey", "success", machine.Namespace.Name).
|
||||||
c.Data(200, "application/json; charset=utf-8", respBody)
|
Inc()
|
||||||
|
ctx.Data(http.StatusOK, "application/json; charset=utf-8", respBody)
|
||||||
log.Info().
|
log.Info().
|
||||||
Str("func", "handleAuthKey").
|
Str("func", "handleAuthKey").
|
||||||
Str("machine", m.Name).
|
Str("machine", machine.Name).
|
||||||
Str("ip", ip.String()).
|
Str("ip", machine.IPAddress).
|
||||||
Msg("Successfully authenticated via AuthKey")
|
Msg("Successfully authenticated via AuthKey")
|
||||||
}
|
}
|
||||||
|
598
app.go
598
app.go
@ -1,36 +1,76 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||||
|
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
zerolog "github.com/philip-bui/grpc-zerolog"
|
||||||
|
zl "github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/soheilhy/cmux"
|
||||||
ginprometheus "github.com/zsais/go-gin-prometheus"
|
ginprometheus "github.com/zsais/go-gin-prometheus"
|
||||||
"golang.org/x/crypto/acme"
|
"golang.org/x/crypto/acme"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
"google.golang.org/grpc/peer"
|
||||||
|
"google.golang.org/grpc/reflection"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/dnstype"
|
"tailscale.com/types/dnstype"
|
||||||
"tailscale.com/types/wgkey"
|
"tailscale.com/types/key"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config contains the initial Headscale configuration
|
const (
|
||||||
|
AuthPrefix = "Bearer "
|
||||||
|
Postgres = "postgres"
|
||||||
|
Sqlite = "sqlite3"
|
||||||
|
updateInterval = 5000
|
||||||
|
HTTPReadTimeout = 30 * time.Second
|
||||||
|
privateKeyFileMode = 0o600
|
||||||
|
|
||||||
|
requestedExpiryCacheExpiration = time.Minute * 5
|
||||||
|
requestedExpiryCacheCleanupInterval = time.Minute * 10
|
||||||
|
|
||||||
|
errUnsupportedDatabase = Error("unsupported DB")
|
||||||
|
errUnsupportedLetsEncryptChallengeType = Error(
|
||||||
|
"unknown value for Lets Encrypt challenge type",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config contains the initial Headscale configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ServerURL string
|
ServerURL string
|
||||||
Addr string
|
Addr string
|
||||||
PrivateKeyPath string
|
|
||||||
EphemeralNodeInactivityTimeout time.Duration
|
EphemeralNodeInactivityTimeout time.Duration
|
||||||
IPPrefix netaddr.IPPrefix
|
IPPrefix netaddr.IPPrefix
|
||||||
|
PrivateKeyPath string
|
||||||
BaseDomain string
|
BaseDomain string
|
||||||
|
|
||||||
DERP DERPConfig
|
DERP DERPConfig
|
||||||
@ -55,6 +95,20 @@ type Config struct {
|
|||||||
ACMEEmail string
|
ACMEEmail string
|
||||||
|
|
||||||
DNSConfig *tailcfg.DNSConfig
|
DNSConfig *tailcfg.DNSConfig
|
||||||
|
|
||||||
|
UnixSocket string
|
||||||
|
UnixSocketPermission fs.FileMode
|
||||||
|
|
||||||
|
OIDC OIDCConfig
|
||||||
|
|
||||||
|
CLI CLIConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type OIDCConfig struct {
|
||||||
|
Issuer string
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
MatchMap map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type DERPConfig struct {
|
type DERPConfig struct {
|
||||||
@ -64,86 +118,110 @@ type DERPConfig struct {
|
|||||||
UpdateFrequency time.Duration
|
UpdateFrequency time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Headscale represents the base app of the service
|
type CLIConfig struct {
|
||||||
|
Address string
|
||||||
|
APIKey string
|
||||||
|
Insecure bool
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headscale represents the base app of the service.
|
||||||
type Headscale struct {
|
type Headscale struct {
|
||||||
cfg Config
|
cfg Config
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
dbString string
|
dbString string
|
||||||
dbType string
|
dbType string
|
||||||
dbDebug bool
|
dbDebug bool
|
||||||
publicKey *wgkey.Key
|
privateKey *key.MachinePrivate
|
||||||
privateKey *wgkey.Private
|
|
||||||
|
|
||||||
DERPMap *tailcfg.DERPMap
|
DERPMap *tailcfg.DERPMap
|
||||||
|
|
||||||
aclPolicy *ACLPolicy
|
aclPolicy *ACLPolicy
|
||||||
aclRules *[]tailcfg.FilterRule
|
aclRules []tailcfg.FilterRule
|
||||||
|
|
||||||
lastStateChange sync.Map
|
lastStateChange sync.Map
|
||||||
|
|
||||||
|
oidcProvider *oidc.Provider
|
||||||
|
oauth2Config *oauth2.Config
|
||||||
|
oidcStateCache *cache.Cache
|
||||||
|
|
||||||
|
requestedExpiryCache *cache.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHeadscale returns the Headscale app
|
// NewHeadscale returns the Headscale app.
|
||||||
func NewHeadscale(cfg Config) (*Headscale, error) {
|
func NewHeadscale(cfg Config) (*Headscale, error) {
|
||||||
content, err := os.ReadFile(cfg.PrivateKeyPath)
|
privKey, err := readOrCreatePrivateKey(cfg.PrivateKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to read or create private key: %w", err)
|
||||||
}
|
}
|
||||||
privKey, err := wgkey.ParsePrivate(string(content))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pubKey := privKey.Public()
|
|
||||||
|
|
||||||
var dbString string
|
var dbString string
|
||||||
switch cfg.DBtype {
|
switch cfg.DBtype {
|
||||||
case "postgres":
|
case Postgres:
|
||||||
dbString = fmt.Sprintf("host=%s port=%d dbname=%s user=%s password=%s sslmode=disable", cfg.DBhost,
|
dbString = fmt.Sprintf(
|
||||||
cfg.DBport, cfg.DBname, cfg.DBuser, cfg.DBpass)
|
"host=%s port=%d dbname=%s user=%s password=%s sslmode=disable",
|
||||||
case "sqlite3":
|
cfg.DBhost,
|
||||||
|
cfg.DBport,
|
||||||
|
cfg.DBname,
|
||||||
|
cfg.DBuser,
|
||||||
|
cfg.DBpass,
|
||||||
|
)
|
||||||
|
case Sqlite:
|
||||||
dbString = cfg.DBpath
|
dbString = cfg.DBpath
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unsupported DB")
|
return nil, errUnsupportedDatabase
|
||||||
}
|
}
|
||||||
|
|
||||||
h := Headscale{
|
requestedExpiryCache := cache.New(
|
||||||
cfg: cfg,
|
requestedExpiryCacheExpiration,
|
||||||
dbType: cfg.DBtype,
|
requestedExpiryCacheCleanupInterval,
|
||||||
dbString: dbString,
|
)
|
||||||
privateKey: privKey,
|
|
||||||
publicKey: &pubKey,
|
app := Headscale{
|
||||||
aclRules: &tailcfg.FilterAllowAll, // default allowall
|
cfg: cfg,
|
||||||
|
dbType: cfg.DBtype,
|
||||||
|
dbString: dbString,
|
||||||
|
privateKey: privKey,
|
||||||
|
aclRules: tailcfg.FilterAllowAll, // default allowall
|
||||||
|
requestedExpiryCache: requestedExpiryCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.initDB()
|
err = app.initDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.cfg.DNSConfig != nil && h.cfg.DNSConfig.Proxied { // if MagicDNS
|
if cfg.OIDC.Issuer != "" {
|
||||||
magicDNSDomains, err := generateMagicDNSRootDomains(h.cfg.IPPrefix, h.cfg.BaseDomain)
|
err = app.initOIDC()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if app.cfg.DNSConfig != nil && app.cfg.DNSConfig.Proxied { // if MagicDNS
|
||||||
|
magicDNSDomains := generateMagicDNSRootDomains(
|
||||||
|
app.cfg.IPPrefix,
|
||||||
|
)
|
||||||
// we might have routes already from Split DNS
|
// we might have routes already from Split DNS
|
||||||
if h.cfg.DNSConfig.Routes == nil {
|
if app.cfg.DNSConfig.Routes == nil {
|
||||||
h.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver)
|
app.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver)
|
||||||
}
|
}
|
||||||
for _, d := range magicDNSDomains {
|
for _, d := range magicDNSDomains {
|
||||||
h.cfg.DNSConfig.Routes[d.WithoutTrailingDot()] = nil
|
app.cfg.DNSConfig.Routes[d.WithoutTrailingDot()] = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &h, nil
|
return &app, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to our TLS url
|
// Redirect to our TLS url.
|
||||||
func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
|
func (h *Headscale) redirect(w http.ResponseWriter, req *http.Request) {
|
||||||
target := h.cfg.ServerURL + req.URL.RequestURI()
|
target := h.cfg.ServerURL + req.URL.RequestURI()
|
||||||
http.Redirect(w, req, target, http.StatusFound)
|
http.Redirect(w, req, target, http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// expireEphemeralNodes deletes ephemeral machine records that have not been
|
// expireEphemeralNodes deletes ephemeral machine records that have not been
|
||||||
// seen for longer than h.cfg.EphemeralNodeInactivityTimeout
|
// seen for longer than h.cfg.EphemeralNodeInactivityTimeout.
|
||||||
func (h *Headscale) expireEphemeralNodes(milliSeconds int64) {
|
func (h *Headscale) expireEphemeralNodes(milliSeconds int64) {
|
||||||
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
|
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
@ -155,33 +233,46 @@ func (h *Headscale) expireEphemeralNodesWorker() {
|
|||||||
namespaces, err := h.ListNamespaces()
|
namespaces, err := h.ListNamespaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Error listing namespaces")
|
log.Error().Err(err).Msg("Error listing namespaces")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, ns := range *namespaces {
|
|
||||||
machines, err := h.ListMachinesInNamespace(ns.Name)
|
for _, namespace := range namespaces {
|
||||||
|
machines, err := h.ListMachinesInNamespace(namespace.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("namespace", ns.Name).Msg("Error listing machines in namespace")
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("namespace", namespace.Name).
|
||||||
|
Msg("Error listing machines in namespace")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, m := range *machines {
|
|
||||||
if m.AuthKey != nil && m.LastSeen != nil && m.AuthKey.Ephemeral &&
|
for _, machine := range machines {
|
||||||
time.Now().After(m.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) {
|
if machine.AuthKey != nil && machine.LastSeen != nil &&
|
||||||
log.Info().Str("machine", m.Name).Msg("Ephemeral client removed from database")
|
machine.AuthKey.Ephemeral &&
|
||||||
err = h.db.Unscoped().Delete(m).Error
|
time.Now().
|
||||||
|
After(machine.LastSeen.Add(h.cfg.EphemeralNodeInactivityTimeout)) {
|
||||||
|
log.Info().
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Msg("Ephemeral client removed from database")
|
||||||
|
|
||||||
|
err = h.db.Unscoped().Delete(machine).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Err(err).
|
Err(err).
|
||||||
Str("machine", m.Name).
|
Str("machine", machine.Name).
|
||||||
Msg("🤮 Cannot delete ephemeral machine from the database")
|
Msg("🤮 Cannot delete ephemeral machine from the database")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.setLastStateChangeToNow(ns.Name)
|
|
||||||
|
h.setLastStateChangeToNow(namespace.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchForKVUpdates checks the KV DB table for requests to perform tailnet upgrades
|
// WatchForKVUpdates checks the KV DB table for requests to perform tailnet upgrades
|
||||||
// This is a way to communitate the CLI with the headscale server
|
// This is a way to communitate the CLI with the headscale server.
|
||||||
func (h *Headscale) watchForKVUpdates(milliSeconds int64) {
|
func (h *Headscale) watchForKVUpdates(milliSeconds int64) {
|
||||||
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
|
ticker := time.NewTicker(time.Duration(milliSeconds) * time.Millisecond)
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
@ -194,24 +285,234 @@ func (h *Headscale) watchForKVUpdatesWorker() {
|
|||||||
// more functions will come here in the future
|
// more functions will come here in the future
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve launches a GIN server with the Headscale API
|
func (h *Headscale) grpcAuthenticationInterceptor(ctx context.Context,
|
||||||
|
req interface{},
|
||||||
|
info *grpc.UnaryServerInfo,
|
||||||
|
handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
|
// Check if the request is coming from the on-server client.
|
||||||
|
// This is not secure, but it is to maintain maintainability
|
||||||
|
// with the "legacy" database-based client
|
||||||
|
// It is also neede for grpc-gateway to be able to connect to
|
||||||
|
// the server
|
||||||
|
client, _ := peer.FromContext(ctx)
|
||||||
|
|
||||||
|
log.Trace().
|
||||||
|
Caller().
|
||||||
|
Str("client_address", client.Addr.String()).
|
||||||
|
Msg("Client is trying to authenticate")
|
||||||
|
|
||||||
|
meta, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Str("client_address", client.Addr.String()).
|
||||||
|
Msg("Retrieving metadata is failed")
|
||||||
|
|
||||||
|
return ctx, status.Errorf(
|
||||||
|
codes.InvalidArgument,
|
||||||
|
"Retrieving metadata is failed",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
authHeader, ok := meta["authorization"]
|
||||||
|
if !ok {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Str("client_address", client.Addr.String()).
|
||||||
|
Msg("Authorization token is not supplied")
|
||||||
|
|
||||||
|
return ctx, status.Errorf(
|
||||||
|
codes.Unauthenticated,
|
||||||
|
"Authorization token is not supplied",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := authHeader[0]
|
||||||
|
|
||||||
|
if !strings.HasPrefix(token, AuthPrefix) {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Str("client_address", client.Addr.String()).
|
||||||
|
Msg(`missing "Bearer " prefix in "Authorization" header`)
|
||||||
|
|
||||||
|
return ctx, status.Error(
|
||||||
|
codes.Unauthenticated,
|
||||||
|
`missing "Bearer " prefix in "Authorization" header`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(kradalby): Implement API key backend:
|
||||||
|
// - Table in the DB
|
||||||
|
// - Key name
|
||||||
|
// - Encrypted
|
||||||
|
// - Expiry
|
||||||
|
//
|
||||||
|
// Currently all other than localhost traffic is unauthorized, this is intentional to allow
|
||||||
|
// us to make use of gRPC for our CLI, but not having to implement any of the remote capabilities
|
||||||
|
// and API key auth
|
||||||
|
return ctx, status.Error(
|
||||||
|
codes.Unauthenticated,
|
||||||
|
"Authentication is not implemented yet",
|
||||||
|
)
|
||||||
|
|
||||||
|
// if strings.TrimPrefix(token, AUTH_PREFIX) != a.Token {
|
||||||
|
// log.Error().Caller().Str("client_address", p.Addr.String()).Msg("invalid token")
|
||||||
|
// return ctx, status.Error(codes.Unauthenticated, "invalid token")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return handler(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) httpAuthenticationMiddleware(ctx *gin.Context) {
|
||||||
|
log.Trace().
|
||||||
|
Caller().
|
||||||
|
Str("client_address", ctx.ClientIP()).
|
||||||
|
Msg("HTTP authentication invoked")
|
||||||
|
|
||||||
|
authHeader := ctx.GetHeader("authorization")
|
||||||
|
|
||||||
|
if !strings.HasPrefix(authHeader, AuthPrefix) {
|
||||||
|
log.Error().
|
||||||
|
Caller().
|
||||||
|
Str("client_address", ctx.ClientIP()).
|
||||||
|
Msg(`missing "Bearer " prefix in "Authorization" header`)
|
||||||
|
ctx.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
|
||||||
|
// TODO(kradalby): Implement API key backend
|
||||||
|
// Currently all traffic is unauthorized, this is intentional to allow
|
||||||
|
// us to make use of gRPC for our CLI, but not having to implement any of the remote capabilities
|
||||||
|
// and API key auth
|
||||||
|
//
|
||||||
|
// if strings.TrimPrefix(authHeader, AUTH_PREFIX) != a.Token {
|
||||||
|
// log.Error().Caller().Str("client_address", c.ClientIP()).Msg("invalid token")
|
||||||
|
// c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error", "unauthorized"})
|
||||||
|
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureUnixSocketIsAbsent will check if the given path for headscales unix socket is clear
|
||||||
|
// and will remove it if it is not.
|
||||||
|
func (h *Headscale) ensureUnixSocketIsAbsent() error {
|
||||||
|
// File does not exist, all fine
|
||||||
|
if _, err := os.Stat(h.cfg.UnixSocket); errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Remove(h.cfg.UnixSocket)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve launches a GIN server with the Headscale API.
|
||||||
func (h *Headscale) Serve() error {
|
func (h *Headscale) Serve() error {
|
||||||
r := gin.Default()
|
|
||||||
|
|
||||||
p := ginprometheus.NewPrometheus("gin")
|
|
||||||
p.Use(r)
|
|
||||||
|
|
||||||
r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"healthy": "ok"}) })
|
|
||||||
r.GET("/key", h.KeyHandler)
|
|
||||||
r.GET("/register", h.RegisterWebAPI)
|
|
||||||
r.POST("/machine/:id/map", h.PollNetMapHandler)
|
|
||||||
r.POST("/machine/:id", h.RegistrationHandler)
|
|
||||||
r.GET("/apple", h.AppleMobileConfig)
|
|
||||||
r.GET("/apple/:platform", h.ApplePlatformConfig)
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
go h.watchForKVUpdates(5000)
|
ctx := context.Background()
|
||||||
go h.expireEphemeralNodes(5000)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err = h.ensureUnixSocketIsAbsent()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to remove old socket file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
socketListener, err := net.Listen("unix", h.cfg.UnixSocket)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set up gRPC socket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change socket permissions
|
||||||
|
if err := os.Chmod(h.cfg.UnixSocket, h.cfg.UnixSocketPermission); err != nil {
|
||||||
|
return fmt.Errorf("failed change permission of gRPC socket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle common process-killing signals so we can gracefully shut down:
|
||||||
|
sigc := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigc, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go func(c chan os.Signal) {
|
||||||
|
// Wait for a SIGINT or SIGKILL:
|
||||||
|
sig := <-c
|
||||||
|
log.Printf("Caught signal %s: shutting down.", sig)
|
||||||
|
// Stop listening (and unlink the socket if unix type):
|
||||||
|
socketListener.Close()
|
||||||
|
// And we're done:
|
||||||
|
os.Exit(0)
|
||||||
|
}(sigc)
|
||||||
|
|
||||||
|
networkListener, err := net.Listen("tcp", h.cfg.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to bind to TCP address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the cmux object that will multiplex 2 protocols on the same port.
|
||||||
|
// The two following listeners will be served on the same port below gracefully.
|
||||||
|
networkMutex := cmux.New(networkListener)
|
||||||
|
// Match gRPC requests here
|
||||||
|
grpcListener := networkMutex.MatchWithWriters(
|
||||||
|
cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"),
|
||||||
|
cmux.HTTP2MatchHeaderFieldSendSettings(
|
||||||
|
"content-type",
|
||||||
|
"application/grpc+proto",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Otherwise match regular http requests.
|
||||||
|
httpListener := networkMutex.Match(cmux.Any())
|
||||||
|
|
||||||
|
grpcGatewayMux := runtime.NewServeMux()
|
||||||
|
|
||||||
|
// Make the grpc-gateway connect to grpc over socket
|
||||||
|
grpcGatewayConn, err := grpc.Dial(
|
||||||
|
h.cfg.UnixSocket,
|
||||||
|
[]grpc.DialOption{
|
||||||
|
grpc.WithInsecure(),
|
||||||
|
grpc.WithContextDialer(GrpcSocketDialer),
|
||||||
|
}...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the gRPC server over localhost to skip
|
||||||
|
// the authentication.
|
||||||
|
err = v1.RegisterHeadscaleServiceHandler(ctx, grpcGatewayMux, grpcGatewayConn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
router := gin.Default()
|
||||||
|
|
||||||
|
prometheus := ginprometheus.NewPrometheus("gin")
|
||||||
|
prometheus.Use(router)
|
||||||
|
|
||||||
|
router.GET(
|
||||||
|
"/health",
|
||||||
|
func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"healthy": "ok"}) },
|
||||||
|
)
|
||||||
|
router.GET("/key", h.KeyHandler)
|
||||||
|
router.GET("/register", h.RegisterWebAPI)
|
||||||
|
router.POST("/machine/:id/map", h.PollNetMapHandler)
|
||||||
|
router.POST("/machine/:id", h.RegistrationHandler)
|
||||||
|
router.GET("/oidc/register/:mkey", h.RegisterOIDC)
|
||||||
|
router.GET("/oidc/callback", h.OIDCCallback)
|
||||||
|
router.GET("/apple", h.AppleMobileConfig)
|
||||||
|
router.GET("/apple/:platform", h.ApplePlatformConfig)
|
||||||
|
router.GET("/swagger", SwaggerUI)
|
||||||
|
router.GET("/swagger/v1/openapiv2.json", SwaggerAPIv1)
|
||||||
|
|
||||||
|
api := router.Group("/api")
|
||||||
|
api.Use(h.httpAuthenticationMiddleware)
|
||||||
|
{
|
||||||
|
api.Any("/v1/*any", gin.WrapF(grpcGatewayMux.ServeHTTP))
|
||||||
|
}
|
||||||
|
|
||||||
|
router.NoRoute(stdoutHandler)
|
||||||
|
|
||||||
// Fetch an initial DERP Map before we start serving
|
// Fetch an initial DERP Map before we start serving
|
||||||
h.DERPMap = GetDERPMap(h.cfg.DERP)
|
h.DERPMap = GetDERPMap(h.cfg.DERP)
|
||||||
@ -222,10 +523,14 @@ func (h *Headscale) Serve() error {
|
|||||||
go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
|
go h.scheduledDERPMapUpdateWorker(derpMapCancelChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &http.Server{
|
// I HATE THIS
|
||||||
|
go h.watchForKVUpdates(updateInterval)
|
||||||
|
go h.expireEphemeralNodes(updateInterval)
|
||||||
|
|
||||||
|
httpServer := &http.Server{
|
||||||
Addr: h.cfg.Addr,
|
Addr: h.cfg.Addr,
|
||||||
Handler: r,
|
Handler: router,
|
||||||
ReadTimeout: 30 * time.Second,
|
ReadTimeout: HTTPReadTimeout,
|
||||||
// Go does not handle timeouts in HTTP very well, and there is
|
// Go does not handle timeouts in HTTP very well, and there is
|
||||||
// no good way to handle streaming timeouts, therefore we need to
|
// no good way to handle streaming timeouts, therefore we need to
|
||||||
// keep this at unlimited and be careful to clean up connections
|
// keep this at unlimited and be careful to clean up connections
|
||||||
@ -233,12 +538,78 @@ func (h *Headscale) Serve() error {
|
|||||||
WriteTimeout: 0,
|
WriteTimeout: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if zl.GlobalLevel() == zl.TraceLevel {
|
||||||
|
zerolog.RespLog = true
|
||||||
|
} else {
|
||||||
|
zerolog.RespLog = false
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcOptions := []grpc.ServerOption{
|
||||||
|
grpc.UnaryInterceptor(
|
||||||
|
grpc_middleware.ChainUnaryServer(
|
||||||
|
h.grpcAuthenticationInterceptor,
|
||||||
|
zerolog.NewUnaryServerInterceptor(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := h.getTLSSettings()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to set up TLS configuration")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsConfig != nil {
|
||||||
|
httpServer.TLSConfig = tlsConfig
|
||||||
|
|
||||||
|
grpcOptions = append(grpcOptions, grpc.Creds(credentials.NewTLS(tlsConfig)))
|
||||||
|
}
|
||||||
|
|
||||||
|
grpcServer := grpc.NewServer(grpcOptions...)
|
||||||
|
|
||||||
|
// Start the local gRPC server without TLS and without authentication
|
||||||
|
grpcSocket := grpc.NewServer(zerolog.UnaryInterceptor())
|
||||||
|
|
||||||
|
v1.RegisterHeadscaleServiceServer(grpcServer, newHeadscaleV1APIServer(h))
|
||||||
|
v1.RegisterHeadscaleServiceServer(grpcSocket, newHeadscaleV1APIServer(h))
|
||||||
|
reflection.Register(grpcServer)
|
||||||
|
reflection.Register(grpcSocket)
|
||||||
|
|
||||||
|
errorGroup := new(errgroup.Group)
|
||||||
|
|
||||||
|
errorGroup.Go(func() error { return grpcSocket.Serve(socketListener) })
|
||||||
|
|
||||||
|
// TODO(kradalby): Verify if we need the same TLS setup for gRPC as HTTP
|
||||||
|
errorGroup.Go(func() error { return grpcServer.Serve(grpcListener) })
|
||||||
|
|
||||||
|
if tlsConfig != nil {
|
||||||
|
errorGroup.Go(func() error {
|
||||||
|
tlsl := tls.NewListener(httpListener, tlsConfig)
|
||||||
|
|
||||||
|
return httpServer.Serve(tlsl)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
errorGroup.Go(func() error { return httpServer.Serve(httpListener) })
|
||||||
|
}
|
||||||
|
|
||||||
|
errorGroup.Go(func() error { return networkMutex.Serve() })
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Msgf("listening and serving (multiplexed HTTP and gRPC) on: %s", h.cfg.Addr)
|
||||||
|
|
||||||
|
return errorGroup.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Headscale) getTLSSettings() (*tls.Config, error) {
|
||||||
|
var err error
|
||||||
if h.cfg.TLSLetsEncryptHostname != "" {
|
if h.cfg.TLSLetsEncryptHostname != "" {
|
||||||
if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
|
if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
|
||||||
log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
|
log.Warn().
|
||||||
|
Msg("Listening with TLS but ServerURL does not start with https://")
|
||||||
}
|
}
|
||||||
|
|
||||||
m := autocert.Manager{
|
certManager := autocert.Manager{
|
||||||
Prompt: autocert.AcceptTOS,
|
Prompt: autocert.AcceptTOS,
|
||||||
HostPolicy: autocert.HostWhitelist(h.cfg.TLSLetsEncryptHostname),
|
HostPolicy: autocert.HostWhitelist(h.cfg.TLSLetsEncryptHostname),
|
||||||
Cache: autocert.DirCache(h.cfg.TLSLetsEncryptCacheDir),
|
Cache: autocert.DirCache(h.cfg.TLSLetsEncryptCacheDir),
|
||||||
@ -248,38 +619,48 @@ func (h *Headscale) Serve() error {
|
|||||||
Email: h.cfg.ACMEEmail,
|
Email: h.cfg.ACMEEmail,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.TLSConfig = m.TLSConfig()
|
switch h.cfg.TLSLetsEncryptChallengeType {
|
||||||
|
case "TLS-ALPN-01":
|
||||||
if h.cfg.TLSLetsEncryptChallengeType == "TLS-ALPN-01" {
|
|
||||||
// Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737)
|
// Configuration via autocert with TLS-ALPN-01 (https://tools.ietf.org/html/rfc8737)
|
||||||
// The RFC requires that the validation is done on port 443; in other words, headscale
|
// The RFC requires that the validation is done on port 443; in other words, headscale
|
||||||
// must be reachable on port 443.
|
// must be reachable on port 443.
|
||||||
err = s.ListenAndServeTLS("", "")
|
return certManager.TLSConfig(), nil
|
||||||
} else if h.cfg.TLSLetsEncryptChallengeType == "HTTP-01" {
|
|
||||||
|
case "HTTP-01":
|
||||||
// Configuration via autocert with HTTP-01. This requires listening on
|
// Configuration via autocert with HTTP-01. This requires listening on
|
||||||
// port 80 for the certificate validation in addition to the headscale
|
// port 80 for the certificate validation in addition to the headscale
|
||||||
// service, which can be configured to run on any other port.
|
// service, which can be configured to run on any other port.
|
||||||
go func() {
|
go func() {
|
||||||
log.Fatal().
|
log.Fatal().
|
||||||
Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, m.HTTPHandler(http.HandlerFunc(h.redirect)))).
|
Err(http.ListenAndServe(h.cfg.TLSLetsEncryptListen, certManager.HTTPHandler(http.HandlerFunc(h.redirect)))).
|
||||||
Msg("failed to set up a HTTP server")
|
Msg("failed to set up a HTTP server")
|
||||||
}()
|
}()
|
||||||
err = s.ListenAndServeTLS("", "")
|
|
||||||
} else {
|
return certManager.TLSConfig(), nil
|
||||||
return errors.New("unknown value for TLSLetsEncryptChallengeType")
|
|
||||||
|
default:
|
||||||
|
return nil, errUnsupportedLetsEncryptChallengeType
|
||||||
}
|
}
|
||||||
} else if h.cfg.TLSCertPath == "" {
|
} else if h.cfg.TLSCertPath == "" {
|
||||||
if !strings.HasPrefix(h.cfg.ServerURL, "http://") {
|
if !strings.HasPrefix(h.cfg.ServerURL, "http://") {
|
||||||
log.Warn().Msg("Listening without TLS but ServerURL does not start with http://")
|
log.Warn().Msg("Listening without TLS but ServerURL does not start with http://")
|
||||||
}
|
}
|
||||||
err = s.ListenAndServe()
|
|
||||||
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
|
if !strings.HasPrefix(h.cfg.ServerURL, "https://") {
|
||||||
log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
|
log.Warn().Msg("Listening with TLS but ServerURL does not start with https://")
|
||||||
}
|
}
|
||||||
err = s.ListenAndServeTLS(h.cfg.TLSCertPath, h.cfg.TLSKeyPath)
|
tlsConfig := &tls.Config{
|
||||||
|
ClientAuth: tls.RequireAnyClientCert,
|
||||||
|
NextProtos: []string{"http/1.1"},
|
||||||
|
Certificates: make([]tls.Certificate, 1),
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(h.cfg.TLSCertPath, h.cfg.TLSKeyPath)
|
||||||
|
|
||||||
|
return tlsConfig, err
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) setLastStateChangeToNow(namespace string) {
|
func (h *Headscale) setLastStateChangeToNow(namespace string) {
|
||||||
@ -311,3 +692,58 @@ func (h *Headscale) getLastStateChange(namespaces ...string) time.Time {
|
|||||||
return times[0]
|
return times[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stdoutHandler(ctx *gin.Context) {
|
||||||
|
body, _ := io.ReadAll(ctx.Request.Body)
|
||||||
|
|
||||||
|
log.Trace().
|
||||||
|
Interface("header", ctx.Request.Header).
|
||||||
|
Interface("proto", ctx.Request.Proto).
|
||||||
|
Interface("url", ctx.Request.URL).
|
||||||
|
Bytes("body", body).
|
||||||
|
Msg("Request did not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func readOrCreatePrivateKey(path string) (*key.MachinePrivate, error) {
|
||||||
|
privateKey, err := os.ReadFile(path)
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
log.Info().Str("path", path).Msg("No private key file at path, creating...")
|
||||||
|
|
||||||
|
machineKey := key.NewMachine()
|
||||||
|
|
||||||
|
machineKeyStr, err := machineKey.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to convert private key to string for saving: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(path, machineKeyStr, privateKeyFileMode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to save private key to disk: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &machineKey, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read private key file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmedPrivateKey := strings.TrimSpace(string(privateKey))
|
||||||
|
privateKeyEnsurePrefix := PrivateKeyEnsurePrefix(trimmedPrivateKey)
|
||||||
|
|
||||||
|
var machineKey key.MachinePrivate
|
||||||
|
if err = machineKey.UnmarshalText([]byte(privateKeyEnsurePrefix)); err != nil {
|
||||||
|
log.Info().
|
||||||
|
Str("path", path).
|
||||||
|
Msg("This might be due to a legacy (headscale pre-0.12) private key. " +
|
||||||
|
"If the key is in WireGuard format, delete the key and restart headscale. " +
|
||||||
|
"A new key will automatically be generated. All Tailscale clients will have to be restarted")
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("failed to parse private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &machineKey, nil
|
||||||
|
}
|
||||||
|
19
app_test.go
19
app_test.go
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
)
|
)
|
||||||
@ -17,8 +18,10 @@ var _ = check.Suite(&Suite{})
|
|||||||
|
|
||||||
type Suite struct{}
|
type Suite struct{}
|
||||||
|
|
||||||
var tmpDir string
|
var (
|
||||||
var h Headscale
|
tmpDir string
|
||||||
|
app Headscale
|
||||||
|
)
|
||||||
|
|
||||||
func (s *Suite) SetUpTest(c *check.C) {
|
func (s *Suite) SetUpTest(c *check.C) {
|
||||||
s.ResetDB(c)
|
s.ResetDB(c)
|
||||||
@ -41,18 +44,22 @@ func (s *Suite) ResetDB(c *check.C) {
|
|||||||
IPPrefix: netaddr.MustParseIPPrefix("10.27.0.0/23"),
|
IPPrefix: netaddr.MustParseIPPrefix("10.27.0.0/23"),
|
||||||
}
|
}
|
||||||
|
|
||||||
h = Headscale{
|
app = Headscale{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
dbType: "sqlite3",
|
dbType: "sqlite3",
|
||||||
dbString: tmpDir + "/headscale_test.db",
|
dbString: tmpDir + "/headscale_test.db",
|
||||||
|
requestedExpiryCache: cache.New(
|
||||||
|
requestedExpiryCacheExpiration,
|
||||||
|
requestedExpiryCacheCleanupInterval,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
err = h.initDB()
|
err = app.initDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
db, err := h.openDB()
|
db, err := app.openDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
h.db = db
|
app.db = db
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,18 @@ package headscale
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppleMobileConfig shows a simple message in the browser to point to the CLI
|
// AppleMobileConfig shows a simple message in the browser to point to the CLI
|
||||||
// Listens in /register
|
// Listens in /register.
|
||||||
func (h *Headscale) AppleMobileConfig(c *gin.Context) {
|
func (h *Headscale) AppleMobileConfig(ctx *gin.Context) {
|
||||||
t := template.Must(template.New("apple").Parse(`
|
appleTemplate := template.Must(template.New("apple").Parse(`
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h1>Apple configuration profiles</h1>
|
<h1>Apple configuration profiles</h1>
|
||||||
@ -56,7 +55,7 @@ func (h *Headscale) AppleMobileConfig(c *gin.Context) {
|
|||||||
|
|
||||||
<p>Or</p>
|
<p>Or</p>
|
||||||
<p>Use your terminal to configure the default setting for Tailscale by issuing:</p>
|
<p>Use your terminal to configure the default setting for Tailscale by issuing:</p>
|
||||||
<code>defaults write io.tailscale.ipn.macos ControlURL {{.Url}}</code>
|
<code>defaults write io.tailscale.ipn.macos ControlURL {{.URL}}</code>
|
||||||
|
|
||||||
<p>Restart Tailscale.app and log in.</p>
|
<p>Restart Tailscale.app and log in.</p>
|
||||||
|
|
||||||
@ -64,24 +63,29 @@ func (h *Headscale) AppleMobileConfig(c *gin.Context) {
|
|||||||
</html>`))
|
</html>`))
|
||||||
|
|
||||||
config := map[string]interface{}{
|
config := map[string]interface{}{
|
||||||
"Url": h.cfg.ServerURL,
|
"URL": h.cfg.ServerURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload bytes.Buffer
|
var payload bytes.Buffer
|
||||||
if err := t.Execute(&payload, config); err != nil {
|
if err := appleTemplate.Execute(&payload, config); err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("handler", "AppleMobileConfig").
|
Str("handler", "AppleMobileConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not render Apple index template")
|
Msg("Could not render Apple index template")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple index template"))
|
ctx.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Apple index template"),
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data(http.StatusOK, "text/html; charset=utf-8", payload.Bytes())
|
ctx.Data(http.StatusOK, "text/html; charset=utf-8", payload.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
func (h *Headscale) ApplePlatformConfig(ctx *gin.Context) {
|
||||||
platform := c.Param("platform")
|
platform := ctx.Param("platform")
|
||||||
|
|
||||||
id, err := uuid.NewV4()
|
id, err := uuid.NewV4()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -89,23 +93,33 @@ func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
|||||||
Str("handler", "ApplePlatformConfig").
|
Str("handler", "ApplePlatformConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Failed not create UUID")
|
Msg("Failed not create UUID")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Failed to create UUID"))
|
ctx.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Failed to create UUID"),
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
contentId, err := uuid.NewV4()
|
contentID, err := uuid.NewV4()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("handler", "ApplePlatformConfig").
|
Str("handler", "ApplePlatformConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Failed not create UUID")
|
Msg("Failed not create UUID")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Failed to create UUID"))
|
ctx.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Failed to create UUID"),
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
platformConfig := AppleMobilePlatformConfig{
|
platformConfig := AppleMobilePlatformConfig{
|
||||||
UUID: contentId,
|
UUID: contentID,
|
||||||
Url: h.cfg.ServerURL,
|
URL: h.cfg.ServerURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
var payload bytes.Buffer
|
var payload bytes.Buffer
|
||||||
@ -117,7 +131,12 @@ func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
|||||||
Str("handler", "ApplePlatformConfig").
|
Str("handler", "ApplePlatformConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not render Apple macOS template")
|
Msg("Could not render Apple macOS template")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple macOS template"))
|
ctx.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Apple macOS template"),
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ios":
|
case "ios":
|
||||||
@ -126,17 +145,27 @@ func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
|||||||
Str("handler", "ApplePlatformConfig").
|
Str("handler", "ApplePlatformConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not render Apple iOS template")
|
Msg("Could not render Apple iOS template")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple iOS template"))
|
ctx.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Apple iOS template"),
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
c.Data(http.StatusOK, "text/html; charset=utf-8", []byte("Invalid platform, only ios and macos is supported"))
|
ctx.Data(
|
||||||
|
http.StatusOK,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Invalid platform, only ios and macos is supported"),
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config := AppleMobileConfig{
|
config := AppleMobileConfig{
|
||||||
UUID: id,
|
UUID: id,
|
||||||
Url: h.cfg.ServerURL,
|
URL: h.cfg.ServerURL,
|
||||||
Payload: payload.String(),
|
Payload: payload.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,25 +175,35 @@ func (h *Headscale) ApplePlatformConfig(c *gin.Context) {
|
|||||||
Str("handler", "ApplePlatformConfig").
|
Str("handler", "ApplePlatformConfig").
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not render Apple platform template")
|
Msg("Could not render Apple platform template")
|
||||||
c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte("Could not render Apple platform template"))
|
ctx.Data(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
"text/html; charset=utf-8",
|
||||||
|
[]byte("Could not render Apple platform template"),
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data(http.StatusOK, "application/x-apple-aspen-config; charset=utf-8", content.Bytes())
|
ctx.Data(
|
||||||
|
http.StatusOK,
|
||||||
|
"application/x-apple-aspen-config; charset=utf-8",
|
||||||
|
content.Bytes(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppleMobileConfig struct {
|
type AppleMobileConfig struct {
|
||||||
UUID uuid.UUID
|
UUID uuid.UUID
|
||||||
Url string
|
URL string
|
||||||
Payload string
|
Payload string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppleMobilePlatformConfig struct {
|
type AppleMobilePlatformConfig struct {
|
||||||
UUID uuid.UUID
|
UUID uuid.UUID
|
||||||
Url string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
var commonTemplate = template.Must(template.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
|
var commonTemplate = template.Must(
|
||||||
|
template.New("mobileconfig").Parse(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
@ -173,7 +212,7 @@ var commonTemplate = template.Must(template.New("mobileconfig").Parse(`<?xml ver
|
|||||||
<key>PayloadDisplayName</key>
|
<key>PayloadDisplayName</key>
|
||||||
<string>Headscale</string>
|
<string>Headscale</string>
|
||||||
<key>PayloadDescription</key>
|
<key>PayloadDescription</key>
|
||||||
<string>Configure Tailscale login server to: {{.Url}}</string>
|
<string>Configure Tailscale login server to: {{.URL}}</string>
|
||||||
<key>PayloadIdentifier</key>
|
<key>PayloadIdentifier</key>
|
||||||
<string>com.github.juanfont.headscale</string>
|
<string>com.github.juanfont.headscale</string>
|
||||||
<key>PayloadRemovalDisallowed</key>
|
<key>PayloadRemovalDisallowed</key>
|
||||||
@ -187,7 +226,8 @@ var commonTemplate = template.Must(template.New("mobileconfig").Parse(`<?xml ver
|
|||||||
{{.Payload}}
|
{{.Payload}}
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>`))
|
</plist>`),
|
||||||
|
)
|
||||||
|
|
||||||
var iosTemplate = template.Must(template.New("iosTemplate").Parse(`
|
var iosTemplate = template.Must(template.New("iosTemplate").Parse(`
|
||||||
<dict>
|
<dict>
|
||||||
@ -203,7 +243,7 @@ var iosTemplate = template.Must(template.New("iosTemplate").Parse(`
|
|||||||
<true/>
|
<true/>
|
||||||
|
|
||||||
<key>ControlURL</key>
|
<key>ControlURL</key>
|
||||||
<string>{{.Url}}</string>
|
<string>{{.URL}}</string>
|
||||||
</dict>
|
</dict>
|
||||||
`))
|
`))
|
||||||
|
|
||||||
@ -221,6 +261,6 @@ var macosTemplate = template.Must(template.New("macosTemplate").Parse(`
|
|||||||
<true/>
|
<true/>
|
||||||
|
|
||||||
<key>ControlURL</key>
|
<key>ControlURL</key>
|
||||||
<string>{{.Url}}</string>
|
<string>{{.URL}}</string>
|
||||||
</dict>
|
</dict>
|
||||||
`))
|
`))
|
||||||
|
21
buf.gen.yaml
Normal file
21
buf.gen.yaml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
version: v1
|
||||||
|
plugins:
|
||||||
|
- name: go
|
||||||
|
out: gen/go
|
||||||
|
opt:
|
||||||
|
- paths=source_relative
|
||||||
|
- name: go-grpc
|
||||||
|
out: gen/go
|
||||||
|
opt:
|
||||||
|
- paths=source_relative
|
||||||
|
- name: grpc-gateway
|
||||||
|
out: gen/go
|
||||||
|
opt:
|
||||||
|
- paths=source_relative
|
||||||
|
- generate_unbound_methods=true
|
||||||
|
# - name: gorm
|
||||||
|
# out: gen/go
|
||||||
|
# opt:
|
||||||
|
# - paths=source_relative,enums=string,gateway=true
|
||||||
|
- name: openapiv2
|
||||||
|
out: gen/openapiv2
|
40
cli.go
40
cli.go
@ -1,40 +0,0 @@
|
|||||||
package headscale
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"tailscale.com/types/wgkey"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey
|
|
||||||
func (h *Headscale) RegisterMachine(key string, namespace string) (*Machine, error) {
|
|
||||||
ns, err := h.GetNamespace(namespace)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mKey, err := wgkey.ParseHex(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m := Machine{}
|
|
||||||
if result := h.db.First(&m, "machine_key = ?", mKey.HexString()); errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, errors.New("Machine not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.isAlreadyRegistered() {
|
|
||||||
return nil, errors.New("Machine already registered")
|
|
||||||
}
|
|
||||||
|
|
||||||
ip, err := h.getAvailableIP()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m.IPAddress = ip.String()
|
|
||||||
m.NamespaceID = ns.ID
|
|
||||||
m.Registered = true
|
|
||||||
m.RegisterMethod = "cli"
|
|
||||||
h.db.Save(&m)
|
|
||||||
return &m, nil
|
|
||||||
}
|
|
24
cli_test.go
24
cli_test.go
@ -1,31 +1,39 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Suite) TestRegisterMachine(c *check.C) {
|
func (s *Suite) TestRegisterMachine(c *check.C) {
|
||||||
n, err := h.CreateNamespace("test")
|
namespace, err := app.CreateNamespace("test")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
m := Machine{
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
machine := Machine{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
MachineKey: "8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
|
MachineKey: "8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
|
||||||
NodeKey: "bar",
|
NodeKey: "bar",
|
||||||
DiscoKey: "faa",
|
DiscoKey: "faa",
|
||||||
Name: "testmachine",
|
Name: "testmachine",
|
||||||
NamespaceID: n.ID,
|
NamespaceID: namespace.ID,
|
||||||
IPAddress: "10.0.0.1",
|
IPAddress: "10.0.0.1",
|
||||||
|
Expiry: &now,
|
||||||
}
|
}
|
||||||
h.db.Save(&m)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
_, err = h.GetMachine("test", "testmachine")
|
_, err = app.GetMachine("test", "testmachine")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
m2, err := h.RegisterMachine("8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e", n.Name)
|
machineAfterRegistering, err := app.RegisterMachine(
|
||||||
|
"8ce002a935f8c394e55e78fbbb410576575ff8ec5cfa2e627e4b807f1be15b0e",
|
||||||
|
namespace.Name,
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(m2.Registered, check.Equals, true)
|
c.Assert(machineAfterRegistering.Registered, check.Equals, true)
|
||||||
|
|
||||||
_, err = m2.GetHostInfo()
|
_, err = machineAfterRegistering.GetHostInfo()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
132
cmd/headscale/cli/debug.go
Normal file
132
cmd/headscale/cli/debug.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyLength = 64
|
||||||
|
errPreAuthKeyTooShort = Error("key too short, must be 64 hexadecimal characters")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error is used to compare errors as per https://dave.cheney.net/2016/04/07/constant-errors
|
||||||
|
type Error string
|
||||||
|
|
||||||
|
func (e Error) Error() string { return string(e) }
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(debugCmd)
|
||||||
|
|
||||||
|
createNodeCmd.Flags().StringP("name", "", "", "Name")
|
||||||
|
err := createNodeCmd.MarkFlagRequired("name")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("")
|
||||||
|
}
|
||||||
|
createNodeCmd.Flags().StringP("namespace", "n", "", "Namespace")
|
||||||
|
err = createNodeCmd.MarkFlagRequired("namespace")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("")
|
||||||
|
}
|
||||||
|
createNodeCmd.Flags().StringP("key", "k", "", "Key")
|
||||||
|
err = createNodeCmd.MarkFlagRequired("key")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("")
|
||||||
|
}
|
||||||
|
createNodeCmd.Flags().
|
||||||
|
StringSliceP("route", "r", []string{}, "List (or repeated flags) of routes to advertise")
|
||||||
|
|
||||||
|
debugCmd.AddCommand(createNodeCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var debugCmd = &cobra.Command{
|
||||||
|
Use: "debug",
|
||||||
|
Short: "debug and testing commands",
|
||||||
|
Long: "debug contains extra commands used for debugging and testing headscale",
|
||||||
|
}
|
||||||
|
|
||||||
|
var createNodeCmd = &cobra.Command{
|
||||||
|
Use: "create-node",
|
||||||
|
Short: "Create a node (machine) that can be registered with `nodes register <>` command",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
|
namespace, err := cmd.Flags().GetString("namespace")
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
name, err := cmd.Flags().GetString("name")
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting node from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
machineKey, err := cmd.Flags().GetString("key")
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting key from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(machineKey) != keyLength {
|
||||||
|
err = errPreAuthKeyTooShort
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
routes, err := cmd.Flags().GetStringSlice("route")
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting routes from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &v1.DebugCreateMachineRequest{
|
||||||
|
Key: machineKey,
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
Routes: routes,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.DebugCreateMachine(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot create machine: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessOutput(response.Machine, "Machine created", output)
|
||||||
|
},
|
||||||
|
}
|
41
cmd/headscale/cli/generate.go
Normal file
41
cmd/headscale/cli/generate.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"tailscale.com/types/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(generateCmd)
|
||||||
|
generateCmd.AddCommand(generatePrivateKeyCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var generateCmd = &cobra.Command{
|
||||||
|
Use: "generate",
|
||||||
|
Short: "Generate commands",
|
||||||
|
}
|
||||||
|
|
||||||
|
var generatePrivateKeyCmd = &cobra.Command{
|
||||||
|
Use: "private-key",
|
||||||
|
Short: "Generate a private key for the headscale server",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
machineKey := key.NewMachine()
|
||||||
|
|
||||||
|
machineKeyStr, err := machineKey.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting machine key from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessOutput(map[string]string{
|
||||||
|
"private_key": string(machineKeyStr),
|
||||||
|
},
|
||||||
|
string(machineKeyStr), output)
|
||||||
|
},
|
||||||
|
}
|
@ -2,12 +2,14 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
survey "github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/juanfont/headscale"
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -18,6 +20,10 @@ func init() {
|
|||||||
namespaceCmd.AddCommand(renameNamespaceCmd)
|
namespaceCmd.AddCommand(renameNamespaceCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
errMissingParameter = headscale.Error("missing parameters")
|
||||||
|
)
|
||||||
|
|
||||||
var namespaceCmd = &cobra.Command{
|
var namespaceCmd = &cobra.Command{
|
||||||
Use: "namespaces",
|
Use: "namespaces",
|
||||||
Short: "Manage the namespaces of Headscale",
|
Short: "Manage the namespaces of Headscale",
|
||||||
@ -28,26 +34,40 @@ var createNamespaceCmd = &cobra.Command{
|
|||||||
Short: "Creates a new namespace",
|
Short: "Creates a new namespace",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return fmt.Errorf("Missing parameters")
|
return errMissingParameter
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
o, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
h, err := getHeadscaleApp()
|
|
||||||
|
namespaceName := args[0]
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
log.Trace().Interface("client", client).Msg("Obtained gRPC client")
|
||||||
|
|
||||||
|
request := &v1.CreateNamespaceRequest{Name: namespaceName}
|
||||||
|
|
||||||
|
log.Trace().Interface("request", request).Msg("Sending CreateNamespace request")
|
||||||
|
response, err := client.CreateNamespace(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
ErrorOutput(
|
||||||
}
|
err,
|
||||||
namespace, err := h.CreateNamespace(args[0])
|
fmt.Sprintf(
|
||||||
if strings.HasPrefix(o, "json") {
|
"Cannot create namespace: %s",
|
||||||
JsonOutput(namespace, err, o)
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error creating namespace: %s\n", err)
|
SuccessOutput(response.Namespace, "Namespace created", output)
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("Namespace created\n")
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,26 +76,70 @@ var destroyNamespaceCmd = &cobra.Command{
|
|||||||
Short: "Destroys a namespace",
|
Short: "Destroys a namespace",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return fmt.Errorf("Missing parameters")
|
return errMissingParameter
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
o, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
h, err := getHeadscaleApp()
|
|
||||||
if err != nil {
|
namespaceName := args[0]
|
||||||
log.Fatalf("Error initializing: %s", err)
|
|
||||||
|
request := &v1.GetNamespaceRequest{
|
||||||
|
Name: namespaceName,
|
||||||
}
|
}
|
||||||
err = h.DestroyNamespace(args[0])
|
|
||||||
if strings.HasPrefix(o, "json") {
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
JsonOutput(map[string]string{"Result": "Namespace destroyed"}, err, o)
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
_, err := client.GetNamespace(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error destroying namespace: %s\n", err)
|
confirm := false
|
||||||
return
|
force, _ := cmd.Flags().GetBool("force")
|
||||||
|
if !force {
|
||||||
|
prompt := &survey.Confirm{
|
||||||
|
Message: fmt.Sprintf(
|
||||||
|
"Do you want to remove the namespace '%s' and any associated preauthkeys?",
|
||||||
|
namespaceName,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
err := survey.AskOne(prompt, &confirm)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirm || force {
|
||||||
|
request := &v1.DeleteNamespaceRequest{Name: namespaceName}
|
||||||
|
|
||||||
|
response, err := client.DeleteNamespace(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Cannot destroy namespace: %s",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SuccessOutput(response, "Namespace destroyed", output)
|
||||||
|
} else {
|
||||||
|
SuccessOutput(map[string]string{"Result": "Namespace not destroyed"}, "Namespace not destroyed", output)
|
||||||
}
|
}
|
||||||
fmt.Printf("Namespace destroyed\n")
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,28 +147,51 @@ var listNamespacesCmd = &cobra.Command{
|
|||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List all the namespaces",
|
Short: "List all the namespaces",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
o, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
h, err := getHeadscaleApp()
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.ListNamespacesRequest{}
|
||||||
|
|
||||||
|
response, err := client.ListNamespaces(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
ErrorOutput(
|
||||||
}
|
err,
|
||||||
namespaces, err := h.ListNamespaces()
|
fmt.Sprintf("Cannot get namespaces: %s", status.Convert(err).Message()),
|
||||||
if strings.HasPrefix(o, "json") {
|
output,
|
||||||
JsonOutput(namespaces, err, o)
|
)
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d := pterm.TableData{{"ID", "Name", "Created"}}
|
if output != "" {
|
||||||
for _, n := range *namespaces {
|
SuccessOutput(response.Namespaces, "", output)
|
||||||
d = append(d, []string{strconv.FormatUint(uint64(n.ID), 10), n.Name, n.CreatedAt.Format("2006-01-02 15:04:05")})
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
|
||||||
|
tableData := pterm.TableData{{"ID", "Name", "Created"}}
|
||||||
|
for _, namespace := range response.GetNamespaces() {
|
||||||
|
tableData = append(
|
||||||
|
tableData,
|
||||||
|
[]string{
|
||||||
|
namespace.GetId(),
|
||||||
|
namespace.GetName(),
|
||||||
|
namespace.GetCreatedAt().AsTime().Format("2006-01-02 15:04:05"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
err = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -113,26 +200,39 @@ var renameNamespaceCmd = &cobra.Command{
|
|||||||
Use: "rename OLD_NAME NEW_NAME",
|
Use: "rename OLD_NAME NEW_NAME",
|
||||||
Short: "Renames a namespace",
|
Short: "Renames a namespace",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 2 {
|
expectedArguments := 2
|
||||||
return fmt.Errorf("Missing parameters")
|
if len(args) < expectedArguments {
|
||||||
|
return errMissingParameter
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
o, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
h, err := getHeadscaleApp()
|
|
||||||
if err != nil {
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
log.Fatalf("Error initializing: %s", err)
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.RenameNamespaceRequest{
|
||||||
|
OldName: args[0],
|
||||||
|
NewName: args[1],
|
||||||
}
|
}
|
||||||
err = h.RenameNamespace(args[0], args[1])
|
|
||||||
if strings.HasPrefix(o, "json") {
|
response, err := client.RenameNamespace(ctx, request)
|
||||||
JsonOutput(map[string]string{"Result": "Namespace renamed"}, err, o)
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Cannot rename namespace: %s",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error renaming namespace: %s\n", err)
|
SuccessOutput(response.Namespace, "Namespace renamed", output)
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("Namespace renamed\n")
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -4,28 +4,70 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
survey "github.com/AlecAivazis/survey/v2"
|
survey "github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/juanfont/headscale"
|
"github.com/juanfont/headscale"
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"tailscale.com/tailcfg"
|
"google.golang.org/grpc/status"
|
||||||
"tailscale.com/types/wgkey"
|
"tailscale.com/types/key"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(nodeCmd)
|
rootCmd.AddCommand(nodeCmd)
|
||||||
nodeCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
|
listNodesCmd.Flags().StringP("namespace", "n", "", "Filter by namespace")
|
||||||
err := nodeCmd.MarkPersistentFlagRequired("namespace")
|
nodeCmd.AddCommand(listNodesCmd)
|
||||||
|
|
||||||
|
registerNodeCmd.Flags().StringP("namespace", "n", "", "Namespace")
|
||||||
|
err := registerNodeCmd.MarkFlagRequired("namespace")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
registerNodeCmd.Flags().StringP("key", "k", "", "Key")
|
||||||
|
err = registerNodeCmd.MarkFlagRequired("key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf(err.Error())
|
log.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
nodeCmd.AddCommand(listNodesCmd)
|
|
||||||
nodeCmd.AddCommand(registerNodeCmd)
|
nodeCmd.AddCommand(registerNodeCmd)
|
||||||
|
|
||||||
|
expireNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||||
|
err = expireNodeCmd.MarkFlagRequired("identifier")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
nodeCmd.AddCommand(expireNodeCmd)
|
||||||
|
|
||||||
|
deleteNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||||
|
err = deleteNodeCmd.MarkFlagRequired("identifier")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err.Error())
|
||||||
|
}
|
||||||
nodeCmd.AddCommand(deleteNodeCmd)
|
nodeCmd.AddCommand(deleteNodeCmd)
|
||||||
|
|
||||||
|
shareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace")
|
||||||
|
err = shareMachineCmd.MarkFlagRequired("namespace")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
shareMachineCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||||
|
err = shareMachineCmd.MarkFlagRequired("identifier")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err.Error())
|
||||||
|
}
|
||||||
nodeCmd.AddCommand(shareMachineCmd)
|
nodeCmd.AddCommand(shareMachineCmd)
|
||||||
|
|
||||||
|
unshareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace")
|
||||||
|
err = unshareMachineCmd.MarkFlagRequired("namespace")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
unshareMachineCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||||
|
err = unshareMachineCmd.MarkFlagRequired("identifier")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err.Error())
|
||||||
|
}
|
||||||
nodeCmd.AddCommand(unshareMachineCmd)
|
nodeCmd.AddCommand(unshareMachineCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,120 +77,208 @@ var nodeCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var registerNodeCmd = &cobra.Command{
|
var registerNodeCmd = &cobra.Command{
|
||||||
Use: "register machineID",
|
Use: "register",
|
||||||
Short: "Registers a machine to your network",
|
Short: "Registers a machine to your network",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("missing parameters")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
n, err := cmd.Flags().GetString("namespace")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
namespace, err := cmd.Flags().GetString("namespace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output)
|
||||||
}
|
|
||||||
o, _ := cmd.Flags().GetString("output")
|
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error initializing: %s", err)
|
|
||||||
}
|
|
||||||
m, err := h.RegisterMachine(args[0], n)
|
|
||||||
if strings.HasPrefix(o, "json") {
|
|
||||||
JsonOutput(m, err, o)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
machineKey, err := cmd.Flags().GetString("key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Cannot register machine: %s\n", err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting machine key from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("Machine registered\n")
|
|
||||||
|
request := &v1.RegisterMachineRequest{
|
||||||
|
Key: machineKey,
|
||||||
|
Namespace: namespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.RegisterMachine(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Cannot register machine: %s\n",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessOutput(response.Machine, "Machine register", output)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var listNodesCmd = &cobra.Command{
|
var listNodesCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List the nodes in a given namespace",
|
Short: "List nodes",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
n, err := cmd.Flags().GetString("namespace")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
namespace, err := cmd.Flags().GetString("namespace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output)
|
||||||
}
|
|
||||||
o, _ := cmd.Flags().GetString("output")
|
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error initializing: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace, err := h.GetNamespace(n)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error fetching namespace: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
machines, err := h.ListMachinesInNamespace(n)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error fetching machines: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sharedMachines, err := h.ListSharedMachinesInNamespace(n)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error fetching shared machines: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
allMachines := append(*machines, *sharedMachines...)
|
|
||||||
|
|
||||||
if strings.HasPrefix(o, "json") {
|
|
||||||
JsonOutput(allMachines, err, o)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
log.Fatalf("Error getting nodes: %s", err)
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.ListMachinesRequest{
|
||||||
|
Namespace: namespace,
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := nodesToPtables(*namespace, allMachines)
|
response, err := client.ListMachines(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error converting to table: %s", err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot get nodes: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
if output != "" {
|
||||||
|
SuccessOutput(response.Machines, "", output)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tableData, err := nodesToPtables(namespace, response.Machines)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
ErrorOutput(err, fmt.Sprintf("Error converting to table: %s", err), output)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var deleteNodeCmd = &cobra.Command{
|
var expireNodeCmd = &cobra.Command{
|
||||||
Use: "delete ID",
|
Use: "expire",
|
||||||
Short: "Delete a node",
|
Short: "Expire (log out) a machine in your network",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Long: "Expiring a node will keep the node in the database and force it to reauthenticate.",
|
||||||
if len(args) < 1 {
|
Aliases: []string{"logout"},
|
||||||
return fmt.Errorf("missing parameters")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
output, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
h, err := getHeadscaleApp()
|
|
||||||
|
identifier, err := cmd.Flags().GetUint64("identifier")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
id, err := strconv.Atoi(args[0])
|
|
||||||
if err != nil {
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
log.Fatalf("Error converting ID to integer: %s", err)
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.ExpireMachineRequest{
|
||||||
|
MachineId: identifier,
|
||||||
}
|
}
|
||||||
m, err := h.GetMachineByID(uint64(id))
|
|
||||||
|
response, err := client.ExpireMachine(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting node: %s", err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Cannot expire machine: %s\n",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessOutput(response.Machine, "Machine expired", output)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var deleteNodeCmd = &cobra.Command{
|
||||||
|
Use: "delete",
|
||||||
|
Short: "Delete a node",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
|
identifier, err := cmd.Flags().GetUint64("identifier")
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error converting ID to integer: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
getRequest := &v1.GetMachineRequest{
|
||||||
|
MachineId: identifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponse, err := client.GetMachine(ctx, getRequest)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Error getting node node: %s",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRequest := &v1.DeleteMachineRequest{
|
||||||
|
MachineId: identifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
confirm := false
|
confirm := false
|
||||||
force, _ := cmd.Flags().GetBool("force")
|
force, _ := cmd.Flags().GetBool("force")
|
||||||
if !force {
|
if !force {
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Confirm{
|
||||||
Message: fmt.Sprintf("Do you want to remove the node %s?", m.Name),
|
Message: fmt.Sprintf(
|
||||||
|
"Do you want to remove the node %s?",
|
||||||
|
getResponse.GetMachine().Name,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
err = survey.AskOne(prompt, &confirm)
|
err = survey.AskOne(prompt, &confirm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -157,162 +287,250 @@ var deleteNodeCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if confirm || force {
|
if confirm || force {
|
||||||
err = h.DeleteMachine(m)
|
response, err := client.DeleteMachine(ctx, deleteRequest)
|
||||||
if strings.HasPrefix(output, "json") {
|
if output != "" {
|
||||||
JsonOutput(map[string]string{"Result": "Node deleted"}, err, output)
|
SuccessOutput(response, "", output)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error deleting node: %s", err)
|
ErrorOutput(
|
||||||
}
|
err,
|
||||||
fmt.Printf("Node deleted\n")
|
fmt.Sprintf(
|
||||||
} else {
|
"Error deleting node: %s",
|
||||||
if strings.HasPrefix(output, "json") {
|
status.Convert(err).Message(),
|
||||||
JsonOutput(map[string]string{"Result": "Node not deleted"}, err, output)
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("Node not deleted\n")
|
SuccessOutput(
|
||||||
|
map[string]string{"Result": "Node deleted"},
|
||||||
|
"Node deleted",
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
SuccessOutput(map[string]string{"Result": "Node not deleted"}, "Node not deleted", output)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sharingWorker(
|
||||||
|
cmd *cobra.Command,
|
||||||
|
) (string, *v1.Machine, *v1.Namespace, error) {
|
||||||
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
namespaceStr, err := cmd.Flags().GetString("namespace")
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output)
|
||||||
|
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
identifier, err := cmd.Flags().GetUint64("identifier")
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(err, fmt.Sprintf("Error converting ID to integer: %s", err), output)
|
||||||
|
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
machineRequest := &v1.GetMachineRequest{
|
||||||
|
MachineId: identifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
machineResponse, err := client.GetMachine(ctx, machineRequest)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting node node: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
namespaceRequest := &v1.GetNamespaceRequest{
|
||||||
|
Name: namespaceStr,
|
||||||
|
}
|
||||||
|
|
||||||
|
namespaceResponse, err := client.GetNamespace(ctx, namespaceRequest)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting node node: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return output, machineResponse.GetMachine(), namespaceResponse.GetNamespace(), nil
|
||||||
|
}
|
||||||
|
|
||||||
var shareMachineCmd = &cobra.Command{
|
var shareMachineCmd = &cobra.Command{
|
||||||
Use: "share ID namespace",
|
Use: "share",
|
||||||
Short: "Shares a node from the current namespace to the specified one",
|
Short: "Shares a node from the current namespace to the specified one",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 2 {
|
|
||||||
return fmt.Errorf("missing parameters")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
namespace, err := cmd.Flags().GetString("namespace")
|
output, machine, namespace, err := sharingWorker(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
ErrorOutput(
|
||||||
}
|
err,
|
||||||
output, _ := cmd.Flags().GetString("output")
|
fmt.Sprintf("Failed to fetch namespace or machine: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error initializing: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = h.GetNamespace(namespace)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error fetching origin namespace: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
destinationNamespace, err := h.GetNamespace(args[1])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error fetching destination namespace: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(args[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error converting ID to integer: %s", err)
|
|
||||||
}
|
|
||||||
machine, err := h.GetMachineByID(uint64(id))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error getting node: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.AddSharedMachineToNamespace(machine, destinationNamespace)
|
|
||||||
if strings.HasPrefix(output, "json") {
|
|
||||||
JsonOutput(map[string]string{"Result": "Node shared"}, err, output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error sharing node: %s\n", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Node shared!")
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.ShareMachineRequest{
|
||||||
|
MachineId: machine.Id,
|
||||||
|
Namespace: namespace.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.ShareMachine(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error sharing node: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessOutput(response.Machine, "Node shared", output)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var unshareMachineCmd = &cobra.Command{
|
var unshareMachineCmd = &cobra.Command{
|
||||||
Use: "unshare ID",
|
Use: "unshare",
|
||||||
Short: "Unshares a node from the specified namespace",
|
Short: "Unshares a node from the specified namespace",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("missing parameters")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
namespace, err := cmd.Flags().GetString("namespace")
|
output, machine, namespace, err := sharingWorker(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
ErrorOutput(
|
||||||
}
|
err,
|
||||||
output, _ := cmd.Flags().GetString("output")
|
fmt.Sprintf("Failed to fetch namespace or machine: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error initializing: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := h.GetNamespace(namespace)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error fetching namespace: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := strconv.Atoi(args[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error converting ID to integer: %s", err)
|
|
||||||
}
|
|
||||||
machine, err := h.GetMachineByID(uint64(id))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error getting node: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.RemoveSharedMachineFromNamespace(machine, n)
|
|
||||||
if strings.HasPrefix(output, "json") {
|
|
||||||
JsonOutput(map[string]string{"Result": "Node unshared"}, err, output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error unsharing node: %s\n", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Node unshared!")
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.UnshareMachineRequest{
|
||||||
|
MachineId: machine.Id,
|
||||||
|
Namespace: namespace.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.UnshareMachine(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error unsharing node: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessOutput(response.Machine, "Node unshared", output)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodesToPtables(currentNamespace headscale.Namespace, machines []headscale.Machine) (pterm.TableData, error) {
|
func nodesToPtables(
|
||||||
d := pterm.TableData{{"ID", "Name", "NodeKey", "Namespace", "IP address", "Ephemeral", "Last seen", "Online"}}
|
currentNamespace string,
|
||||||
|
machines []*v1.Machine,
|
||||||
|
) (pterm.TableData, error) {
|
||||||
|
tableData := pterm.TableData{
|
||||||
|
{
|
||||||
|
"ID",
|
||||||
|
"Name",
|
||||||
|
"NodeKey",
|
||||||
|
"Namespace",
|
||||||
|
"IP address",
|
||||||
|
"Ephemeral",
|
||||||
|
"Last seen",
|
||||||
|
"Online",
|
||||||
|
"Expired",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
for _, machine := range machines {
|
for _, machine := range machines {
|
||||||
var ephemeral bool
|
var ephemeral bool
|
||||||
if machine.AuthKey != nil && machine.AuthKey.Ephemeral {
|
if machine.PreAuthKey != nil && machine.PreAuthKey.Ephemeral {
|
||||||
ephemeral = true
|
ephemeral = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastSeen time.Time
|
var lastSeen time.Time
|
||||||
var lastSeenTime string
|
var lastSeenTime string
|
||||||
if machine.LastSeen != nil {
|
if machine.LastSeen != nil {
|
||||||
lastSeen = *machine.LastSeen
|
lastSeen = machine.LastSeen.AsTime()
|
||||||
lastSeenTime = lastSeen.Format("2006-01-02 15:04:05")
|
lastSeenTime = lastSeen.Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
nKey, err := wgkey.ParseHex(machine.NodeKey)
|
|
||||||
|
var expiry time.Time
|
||||||
|
if machine.Expiry != nil {
|
||||||
|
expiry = machine.Expiry.AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeKey key.NodePublic
|
||||||
|
err := nodeKey.UnmarshalText(
|
||||||
|
[]byte(headscale.NodePublicKeyEnsurePrefix(machine.NodeKey)),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nodeKey := tailcfg.NodeKey(nKey)
|
|
||||||
|
|
||||||
var online string
|
var online string
|
||||||
if lastSeen.After(time.Now().Add(-5 * time.Minute)) { // TODO: Find a better way to reliably show if online
|
if lastSeen.After(
|
||||||
online = pterm.LightGreen("true")
|
time.Now().Add(-5 * time.Minute),
|
||||||
|
) { // TODO: Find a better way to reliably show if online
|
||||||
|
online = pterm.LightGreen("online")
|
||||||
} else {
|
} else {
|
||||||
online = pterm.LightRed("false")
|
online = pterm.LightRed("offline")
|
||||||
|
}
|
||||||
|
|
||||||
|
var expired string
|
||||||
|
if expiry.IsZero() || expiry.After(time.Now()) {
|
||||||
|
expired = pterm.LightGreen("no")
|
||||||
|
} else {
|
||||||
|
expired = pterm.LightRed("yes")
|
||||||
}
|
}
|
||||||
|
|
||||||
var namespace string
|
var namespace string
|
||||||
if currentNamespace.ID == machine.NamespaceID {
|
if currentNamespace == "" || (currentNamespace == machine.Namespace.Name) {
|
||||||
namespace = pterm.LightMagenta(machine.Namespace.Name)
|
namespace = pterm.LightMagenta(machine.Namespace.Name)
|
||||||
} else {
|
} else {
|
||||||
|
// Shared into this namespace
|
||||||
namespace = pterm.LightYellow(machine.Namespace.Name)
|
namespace = pterm.LightYellow(machine.Namespace.Name)
|
||||||
}
|
}
|
||||||
d = append(d, []string{strconv.FormatUint(machine.ID, 10), machine.Name, nodeKey.ShortString(), namespace, machine.IPAddress, strconv.FormatBool(ephemeral), lastSeenTime, online})
|
tableData = append(
|
||||||
|
tableData,
|
||||||
|
[]string{
|
||||||
|
strconv.FormatUint(machine.Id, headscale.Base10),
|
||||||
|
machine.Name,
|
||||||
|
nodeKey.ShortString(),
|
||||||
|
namespace,
|
||||||
|
machine.IpAddress,
|
||||||
|
strconv.FormatBool(ephemeral),
|
||||||
|
lastSeenTime,
|
||||||
|
online,
|
||||||
|
expired,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return d, nil
|
|
||||||
|
return tableData, nil
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,18 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hako/durafmt"
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultPreAuthKeyExpiry = 1 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -17,14 +21,17 @@ func init() {
|
|||||||
preauthkeysCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
|
preauthkeysCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
|
||||||
err := preauthkeysCmd.MarkPersistentFlagRequired("namespace")
|
err := preauthkeysCmd.MarkPersistentFlagRequired("namespace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf(err.Error())
|
log.Fatal().Err(err).Msg("")
|
||||||
}
|
}
|
||||||
preauthkeysCmd.AddCommand(listPreAuthKeys)
|
preauthkeysCmd.AddCommand(listPreAuthKeys)
|
||||||
preauthkeysCmd.AddCommand(createPreAuthKeyCmd)
|
preauthkeysCmd.AddCommand(createPreAuthKeyCmd)
|
||||||
preauthkeysCmd.AddCommand(expirePreAuthKeyCmd)
|
preauthkeysCmd.AddCommand(expirePreAuthKeyCmd)
|
||||||
createPreAuthKeyCmd.PersistentFlags().Bool("reusable", false, "Make the preauthkey reusable")
|
createPreAuthKeyCmd.PersistentFlags().
|
||||||
createPreAuthKeyCmd.PersistentFlags().Bool("ephemeral", false, "Preauthkey for ephemeral nodes")
|
Bool("reusable", false, "Make the preauthkey reusable")
|
||||||
createPreAuthKeyCmd.Flags().StringP("expiration", "e", "", "Human-readable expiration of the key (30m, 24h, 365d...)")
|
createPreAuthKeyCmd.PersistentFlags().
|
||||||
|
Bool("ephemeral", false, "Preauthkey for ephemeral nodes")
|
||||||
|
createPreAuthKeyCmd.Flags().
|
||||||
|
DurationP("expiration", "e", DefaultPreAuthKeyExpiry, "Human-readable expiration of the key (30m, 24h, 365d...)")
|
||||||
}
|
}
|
||||||
|
|
||||||
var preauthkeysCmd = &cobra.Command{
|
var preauthkeysCmd = &cobra.Command{
|
||||||
@ -36,55 +43,76 @@ var listPreAuthKeys = &cobra.Command{
|
|||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List the preauthkeys for this namespace",
|
Short: "List the preauthkeys for this namespace",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
n, err := cmd.Flags().GetString("namespace")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
|
||||||
}
|
|
||||||
o, _ := cmd.Flags().GetString("output")
|
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
namespace, err := cmd.Flags().GetString("namespace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output)
|
||||||
}
|
|
||||||
keys, err := h.GetPreAuthKeys(n)
|
|
||||||
if strings.HasPrefix(o, "json") {
|
|
||||||
JsonOutput(keys, err, o)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.ListPreAuthKeysRequest{
|
||||||
|
Namespace: namespace,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.ListPreAuthKeys(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error getting the list of keys: %s\n", err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting the list of keys: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d := pterm.TableData{{"ID", "Key", "Reusable", "Ephemeral", "Used", "Expiration", "Created"}}
|
if output != "" {
|
||||||
for _, k := range *keys {
|
SuccessOutput(response.PreAuthKeys, "", output)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tableData := pterm.TableData{
|
||||||
|
{"ID", "Key", "Reusable", "Ephemeral", "Used", "Expiration", "Created"},
|
||||||
|
}
|
||||||
|
for _, key := range response.PreAuthKeys {
|
||||||
expiration := "-"
|
expiration := "-"
|
||||||
if k.Expiration != nil {
|
if key.GetExpiration() != nil {
|
||||||
expiration = k.Expiration.Format("2006-01-02 15:04:05")
|
expiration = key.Expiration.AsTime().Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
|
|
||||||
var reusable string
|
var reusable string
|
||||||
if k.Ephemeral {
|
if key.GetEphemeral() {
|
||||||
reusable = "N/A"
|
reusable = "N/A"
|
||||||
} else {
|
} else {
|
||||||
reusable = fmt.Sprintf("%v", k.Reusable)
|
reusable = fmt.Sprintf("%v", key.GetReusable())
|
||||||
}
|
}
|
||||||
|
|
||||||
d = append(d, []string{
|
tableData = append(tableData, []string{
|
||||||
strconv.FormatUint(k.ID, 10),
|
key.GetId(),
|
||||||
k.Key,
|
key.GetKey(),
|
||||||
reusable,
|
reusable,
|
||||||
strconv.FormatBool(k.Ephemeral),
|
strconv.FormatBool(key.GetEphemeral()),
|
||||||
fmt.Sprintf("%v", k.Used),
|
strconv.FormatBool(key.GetUsed()),
|
||||||
expiration,
|
expiration,
|
||||||
k.CreatedAt.Format("2006-01-02 15:04:05"),
|
key.GetCreatedAt().AsTime().Format("2006-01-02 15:04:05"),
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
err = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -93,40 +121,53 @@ var createPreAuthKeyCmd = &cobra.Command{
|
|||||||
Use: "create",
|
Use: "create",
|
||||||
Short: "Creates a new preauthkey in the specified namespace",
|
Short: "Creates a new preauthkey in the specified namespace",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
n, err := cmd.Flags().GetString("namespace")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
|
||||||
}
|
|
||||||
o, _ := cmd.Flags().GetString("output")
|
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
namespace, err := cmd.Flags().GetString("namespace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reusable, _ := cmd.Flags().GetBool("reusable")
|
reusable, _ := cmd.Flags().GetBool("reusable")
|
||||||
ephemeral, _ := cmd.Flags().GetBool("ephemeral")
|
ephemeral, _ := cmd.Flags().GetBool("ephemeral")
|
||||||
|
|
||||||
e, _ := cmd.Flags().GetString("expiration")
|
log.Trace().
|
||||||
var expiration *time.Time
|
Bool("reusable", reusable).
|
||||||
if e != "" {
|
Bool("ephemeral", ephemeral).
|
||||||
duration, err := durafmt.ParseStringShort(e)
|
Str("namespace", namespace).
|
||||||
if err != nil {
|
Msg("Preparing to create preauthkey")
|
||||||
log.Fatalf("Error parsing expiration: %s", err)
|
|
||||||
}
|
request := &v1.CreatePreAuthKeyRequest{
|
||||||
exp := time.Now().UTC().Add(duration.Duration())
|
Namespace: namespace,
|
||||||
expiration = &exp
|
Reusable: reusable,
|
||||||
|
Ephemeral: ephemeral,
|
||||||
}
|
}
|
||||||
|
|
||||||
k, err := h.CreatePreAuthKey(n, reusable, ephemeral, expiration)
|
duration, _ := cmd.Flags().GetDuration("expiration")
|
||||||
if strings.HasPrefix(o, "json") {
|
expiration := time.Now().UTC().Add(duration)
|
||||||
JsonOutput(k, err, o)
|
|
||||||
return
|
log.Trace().Dur("expiration", duration).Msg("expiration has been set")
|
||||||
}
|
|
||||||
|
request.Expiration = timestamppb.New(expiration)
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
response, err := client.CreatePreAuthKey(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot create Pre Auth Key: %s\n", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("%s\n", k.Key)
|
|
||||||
|
SuccessOutput(response.PreAuthKey, response.PreAuthKey.Key, output)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,40 +176,40 @@ var expirePreAuthKeyCmd = &cobra.Command{
|
|||||||
Short: "Expire a preauthkey",
|
Short: "Expire a preauthkey",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return fmt.Errorf("missing parameters")
|
return errMissingParameter
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
n, err := cmd.Flags().GetString("namespace")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
namespace, err := cmd.Flags().GetString("namespace")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
ErrorOutput(err, fmt.Sprintf("Error getting namespace: %s", err), output)
|
||||||
}
|
|
||||||
o, _ := cmd.Flags().GetString("output")
|
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error initializing: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
k, err := h.GetPreAuthKey(n, args[0])
|
|
||||||
if err != nil {
|
|
||||||
if strings.HasPrefix(o, "json") {
|
|
||||||
JsonOutput(k, err, o)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Fatalf("Error getting the key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.MarkExpirePreAuthKey(k)
|
|
||||||
if strings.HasPrefix(o, "json") {
|
|
||||||
JsonOutput(k, err, o)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
|
defer cancel()
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.ExpirePreAuthKeyRequest{
|
||||||
|
Namespace: namespace,
|
||||||
|
Key: args[0],
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.ExpirePreAuthKey(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot expire Pre Auth Key: %s\n", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("Expired")
|
|
||||||
|
SuccessOutput(response, "Key expired", output)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringP("output", "o", "", "Output format. Empty for human-readable, 'json' or 'json-line'")
|
rootCmd.PersistentFlags().
|
||||||
rootCmd.PersistentFlags().Bool("force", false, "Disable prompts and forces the execution")
|
StringP("output", "o", "", "Output format. Empty for human-readable, 'json', 'json-line' or 'yaml'")
|
||||||
|
rootCmd.PersistentFlags().
|
||||||
|
Bool("force", false, "Disable prompts and forces the execution")
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
|
@ -3,24 +3,35 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strconv"
|
||||||
|
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(routesCmd)
|
rootCmd.AddCommand(routesCmd)
|
||||||
routesCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
|
|
||||||
err := routesCmd.MarkPersistentFlagRequired("namespace")
|
listRoutesCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||||
|
err := listRoutesCmd.MarkFlagRequired("identifier")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
routesCmd.AddCommand(listRoutesCmd)
|
||||||
|
|
||||||
|
enableRouteCmd.Flags().
|
||||||
|
StringSliceP("route", "r", []string{}, "List (or repeated flags) of routes to enable")
|
||||||
|
enableRouteCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
|
||||||
|
err = enableRouteCmd.MarkFlagRequired("identifier")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf(err.Error())
|
log.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
enableRouteCmd.Flags().BoolP("all", "a", false, "Enable all routes advertised by the node")
|
|
||||||
|
|
||||||
routesCmd.AddCommand(listRoutesCmd)
|
|
||||||
routesCmd.AddCommand(enableRouteCmd)
|
routesCmd.AddCommand(enableRouteCmd)
|
||||||
|
|
||||||
|
nodeCmd.AddCommand(routesCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
var routesCmd = &cobra.Command{
|
var routesCmd = &cobra.Command{
|
||||||
@ -29,119 +40,168 @@ var routesCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var listRoutesCmd = &cobra.Command{
|
var listRoutesCmd = &cobra.Command{
|
||||||
Use: "list NODE",
|
Use: "list",
|
||||||
Short: "List the routes exposed by this node",
|
Short: "List routes advertised and enabled by a given node",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("Missing parameters")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
n, err := cmd.Flags().GetString("namespace")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
|
||||||
}
|
|
||||||
o, _ := cmd.Flags().GetString("output")
|
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
machineID, err := cmd.Flags().GetUint64("identifier")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
ErrorOutput(
|
||||||
}
|
err,
|
||||||
|
fmt.Sprintf("Error getting machine id from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
availableRoutes, err := h.GetAdvertisedNodeRoutes(n, args[0])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(o, "json") {
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
// TODO: Add enable/disabled information to this interface
|
defer cancel()
|
||||||
JsonOutput(availableRoutes, err, o)
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.GetMachineRouteRequest{
|
||||||
|
MachineId: machineID,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.GetMachineRoute(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Cannot get nodes: %s", status.Convert(err).Message()),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d := h.RoutesToPtables(n, args[0], *availableRoutes)
|
if output != "" {
|
||||||
|
SuccessOutput(response.Routes, "", output)
|
||||||
|
|
||||||
err = pterm.DefaultTable.WithHasHeader().WithData(d).Render()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tableData := routesToPtables(response.Routes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
ErrorOutput(err, fmt.Sprintf("Error converting to table: %s", err), output)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var enableRouteCmd = &cobra.Command{
|
var enableRouteCmd = &cobra.Command{
|
||||||
Use: "enable node-name route",
|
Use: "enable",
|
||||||
Short: "Allows exposing a route declared by this node to the rest of the nodes",
|
Short: "Set the enabled routes for a given node",
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Long: `This command will take a list of routes that will _replace_
|
||||||
all, err := cmd.Flags().GetBool("all")
|
the current set of routes on a given node.
|
||||||
if err != nil {
|
If you would like to disable a route, simply run the command again, but
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
omit the route you do not want to enable.
|
||||||
}
|
`,
|
||||||
|
|
||||||
if all {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return fmt.Errorf("Missing parameters")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
if len(args) < 2 {
|
|
||||||
return fmt.Errorf("Missing parameters")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
n, err := cmd.Flags().GetString("namespace")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
|
machineID, err := cmd.Flags().GetUint64("identifier")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting machine id from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
o, _ := cmd.Flags().GetString("output")
|
routes, err := cmd.Flags().GetStringSlice("route")
|
||||||
|
|
||||||
all, err := cmd.Flags().GetBool("all")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Error getting routes from flag: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
ctx, client, conn, cancel := getHeadscaleCLIClient()
|
||||||
if err != nil {
|
defer cancel()
|
||||||
log.Fatalf("Error initializing: %s", err)
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := &v1.EnableMachineRoutesRequest{
|
||||||
|
MachineId: machineID,
|
||||||
|
Routes: routes,
|
||||||
}
|
}
|
||||||
|
|
||||||
if all {
|
response, err := client.EnableMachineRoutes(ctx, request)
|
||||||
availableRoutes, err := h.GetAdvertisedNodeRoutes(n, args[0])
|
if err != nil {
|
||||||
if err != nil {
|
ErrorOutput(
|
||||||
fmt.Println(err)
|
err,
|
||||||
return
|
fmt.Sprintf(
|
||||||
}
|
"Cannot register machine: %s\n",
|
||||||
|
status.Convert(err).Message(),
|
||||||
|
),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
for _, availableRoute := range *availableRoutes {
|
return
|
||||||
err = h.EnableNodeRoute(n, args[0], availableRoute.String())
|
}
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(o, "json") {
|
if output != "" {
|
||||||
JsonOutput(availableRoute, err, o)
|
SuccessOutput(response.Routes, "", output)
|
||||||
} else {
|
|
||||||
fmt.Printf("Enabled route %s\n", availableRoute)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = h.EnableNodeRoute(n, args[0], args[1])
|
|
||||||
|
|
||||||
if strings.HasPrefix(o, "json") {
|
return
|
||||||
JsonOutput(args[1], err, o)
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
tableData := routesToPtables(response.Routes)
|
||||||
fmt.Println(err)
|
if err != nil {
|
||||||
return
|
ErrorOutput(err, fmt.Sprintf("Error converting to table: %s", err), output)
|
||||||
}
|
|
||||||
fmt.Printf("Enabled route %s\n", args[1])
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
|
||||||
|
if err != nil {
|
||||||
|
ErrorOutput(
|
||||||
|
err,
|
||||||
|
fmt.Sprintf("Failed to render pterm table: %s", err),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// routesToPtables converts the list of routes to a nice table.
|
||||||
|
func routesToPtables(routes *v1.Routes) pterm.TableData {
|
||||||
|
tableData := pterm.TableData{{"Route", "Enabled"}}
|
||||||
|
|
||||||
|
for _, route := range routes.GetAdvertisedRoutes() {
|
||||||
|
enabled := isStringInSlice(routes.EnabledRoutes, route)
|
||||||
|
|
||||||
|
tableData = append(tableData, []string{route, strconv.FormatBool(enabled)})
|
||||||
|
}
|
||||||
|
|
||||||
|
return tableData
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStringInSlice(strs []string, s string) bool {
|
||||||
|
for _, s2 := range strs {
|
||||||
|
if s == s2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -1,26 +1,33 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/juanfont/headscale"
|
"github.com/juanfont/headscale"
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/dnstype"
|
"tailscale.com/types/dnstype"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrorOutput struct {
|
const (
|
||||||
Error string
|
PermissionFallback = 0o700
|
||||||
}
|
)
|
||||||
|
|
||||||
func LoadConfig(path string) error {
|
func LoadConfig(path string) error {
|
||||||
viper.SetConfigName("config")
|
viper.SetConfigName("config")
|
||||||
@ -32,6 +39,9 @@ func LoadConfig(path string) error {
|
|||||||
// For testing
|
// For testing
|
||||||
viper.AddConfigPath(path)
|
viper.AddConfigPath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viper.SetEnvPrefix("headscale")
|
||||||
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
viper.SetDefault("tls_letsencrypt_cache_dir", "/var/www/.cache")
|
viper.SetDefault("tls_letsencrypt_cache_dir", "/var/www/.cache")
|
||||||
@ -43,9 +53,14 @@ func LoadConfig(path string) error {
|
|||||||
|
|
||||||
viper.SetDefault("dns_config", nil)
|
viper.SetDefault("dns_config", nil)
|
||||||
|
|
||||||
err := viper.ReadInConfig()
|
viper.SetDefault("unix_socket", "/var/run/headscale.sock")
|
||||||
if err != nil {
|
viper.SetDefault("unix_socket_permission", "0o770")
|
||||||
return fmt.Errorf("Fatal error reading config file: %s \n", err)
|
|
||||||
|
viper.SetDefault("cli.insecure", false)
|
||||||
|
viper.SetDefault("cli.timeout", "5s")
|
||||||
|
|
||||||
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
|
return fmt.Errorf("fatal error reading config file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect any validation errors and return them all at once
|
// Collect any validation errors and return them all at once
|
||||||
@ -73,6 +88,7 @@ func LoadConfig(path string) error {
|
|||||||
errorText += "Fatal config error: server_url must start with https:// or http://\n"
|
errorText += "Fatal config error: server_url must start with https:// or http://\n"
|
||||||
}
|
}
|
||||||
if errorText != "" {
|
if errorText != "" {
|
||||||
|
//nolint
|
||||||
return errors.New(strings.TrimSuffix(errorText, "\n"))
|
return errors.New(strings.TrimSuffix(errorText, "\n"))
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
@ -140,9 +156,14 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) {
|
|||||||
if viper.IsSet("dns_config.restricted_nameservers") {
|
if viper.IsSet("dns_config.restricted_nameservers") {
|
||||||
if len(dnsConfig.Nameservers) > 0 {
|
if len(dnsConfig.Nameservers) > 0 {
|
||||||
dnsConfig.Routes = make(map[string][]dnstype.Resolver)
|
dnsConfig.Routes = make(map[string][]dnstype.Resolver)
|
||||||
restrictedDNS := viper.GetStringMapStringSlice("dns_config.restricted_nameservers")
|
restrictedDNS := viper.GetStringMapStringSlice(
|
||||||
|
"dns_config.restricted_nameservers",
|
||||||
|
)
|
||||||
for domain, restrictedNameservers := range restrictedDNS {
|
for domain, restrictedNameservers := range restrictedDNS {
|
||||||
restrictedResolvers := make([]dnstype.Resolver, len(restrictedNameservers))
|
restrictedResolvers := make(
|
||||||
|
[]dnstype.Resolver,
|
||||||
|
len(restrictedNameservers),
|
||||||
|
)
|
||||||
for index, nameserverStr := range restrictedNameservers {
|
for index, nameserverStr := range restrictedNameservers {
|
||||||
nameserver, err := netaddr.ParseIP(nameserverStr)
|
nameserver, err := netaddr.ParseIP(nameserverStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -199,35 +220,26 @@ func absPath(path string) string {
|
|||||||
path = filepath.Join(dir, path)
|
path = filepath.Join(dir, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHeadscaleApp() (*headscale.Headscale, error) {
|
func getHeadscaleConfig() headscale.Config {
|
||||||
// Minimum inactivity time out is keepalive timeout (60s) plus a few seconds
|
|
||||||
// to avoid races
|
|
||||||
minInactivityTimeout, _ := time.ParseDuration("65s")
|
|
||||||
if viper.GetDuration("ephemeral_node_inactivity_timeout") <= minInactivityTimeout {
|
|
||||||
err := fmt.Errorf(
|
|
||||||
"ephemeral_node_inactivity_timeout (%s) is set too low, must be more than %s\n",
|
|
||||||
viper.GetString("ephemeral_node_inactivity_timeout"),
|
|
||||||
minInactivityTimeout,
|
|
||||||
)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsConfig, baseDomain := GetDNSConfig()
|
dnsConfig, baseDomain := GetDNSConfig()
|
||||||
derpConfig := GetDERPConfig()
|
derpConfig := GetDERPConfig()
|
||||||
|
|
||||||
cfg := headscale.Config{
|
return headscale.Config{
|
||||||
ServerURL: viper.GetString("server_url"),
|
ServerURL: viper.GetString("server_url"),
|
||||||
Addr: viper.GetString("listen_addr"),
|
Addr: viper.GetString("listen_addr"),
|
||||||
PrivateKeyPath: absPath(viper.GetString("private_key_path")),
|
|
||||||
IPPrefix: netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")),
|
IPPrefix: netaddr.MustParseIPPrefix(viper.GetString("ip_prefix")),
|
||||||
|
PrivateKeyPath: absPath(viper.GetString("private_key_path")),
|
||||||
BaseDomain: baseDomain,
|
BaseDomain: baseDomain,
|
||||||
|
|
||||||
DERP: derpConfig,
|
DERP: derpConfig,
|
||||||
|
|
||||||
EphemeralNodeInactivityTimeout: viper.GetDuration("ephemeral_node_inactivity_timeout"),
|
EphemeralNodeInactivityTimeout: viper.GetDuration(
|
||||||
|
"ephemeral_node_inactivity_timeout",
|
||||||
|
),
|
||||||
|
|
||||||
DBtype: viper.GetString("db_type"),
|
DBtype: viper.GetString("db_type"),
|
||||||
DBpath: absPath(viper.GetString("db_path")),
|
DBpath: absPath(viper.GetString("db_path")),
|
||||||
@ -237,9 +249,11 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
|
|||||||
DBuser: viper.GetString("db_user"),
|
DBuser: viper.GetString("db_user"),
|
||||||
DBpass: viper.GetString("db_pass"),
|
DBpass: viper.GetString("db_pass"),
|
||||||
|
|
||||||
TLSLetsEncryptHostname: viper.GetString("tls_letsencrypt_hostname"),
|
TLSLetsEncryptHostname: viper.GetString("tls_letsencrypt_hostname"),
|
||||||
TLSLetsEncryptListen: viper.GetString("tls_letsencrypt_listen"),
|
TLSLetsEncryptListen: viper.GetString("tls_letsencrypt_listen"),
|
||||||
TLSLetsEncryptCacheDir: absPath(viper.GetString("tls_letsencrypt_cache_dir")),
|
TLSLetsEncryptCacheDir: absPath(
|
||||||
|
viper.GetString("tls_letsencrypt_cache_dir"),
|
||||||
|
),
|
||||||
TLSLetsEncryptChallengeType: viper.GetString("tls_letsencrypt_challenge_type"),
|
TLSLetsEncryptChallengeType: viper.GetString("tls_letsencrypt_challenge_type"),
|
||||||
|
|
||||||
TLSCertPath: absPath(viper.GetString("tls_cert_path")),
|
TLSCertPath: absPath(viper.GetString("tls_cert_path")),
|
||||||
@ -249,9 +263,46 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
|
|||||||
|
|
||||||
ACMEEmail: viper.GetString("acme_email"),
|
ACMEEmail: viper.GetString("acme_email"),
|
||||||
ACMEURL: viper.GetString("acme_url"),
|
ACMEURL: viper.GetString("acme_url"),
|
||||||
|
|
||||||
|
UnixSocket: viper.GetString("unix_socket"),
|
||||||
|
UnixSocketPermission: GetFileMode("unix_socket_permission"),
|
||||||
|
|
||||||
|
OIDC: headscale.OIDCConfig{
|
||||||
|
Issuer: viper.GetString("oidc.issuer"),
|
||||||
|
ClientID: viper.GetString("oidc.client_id"),
|
||||||
|
ClientSecret: viper.GetString("oidc.client_secret"),
|
||||||
|
},
|
||||||
|
|
||||||
|
CLI: headscale.CLIConfig{
|
||||||
|
Address: viper.GetString("cli.address"),
|
||||||
|
APIKey: viper.GetString("cli.api_key"),
|
||||||
|
Insecure: viper.GetBool("cli.insecure"),
|
||||||
|
Timeout: viper.GetDuration("cli.timeout"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHeadscaleApp() (*headscale.Headscale, error) {
|
||||||
|
// Minimum inactivity time out is keepalive timeout (60s) plus a few seconds
|
||||||
|
// to avoid races
|
||||||
|
minInactivityTimeout, _ := time.ParseDuration("65s")
|
||||||
|
if viper.GetDuration("ephemeral_node_inactivity_timeout") <= minInactivityTimeout {
|
||||||
|
// TODO: Find a better way to return this text
|
||||||
|
//nolint
|
||||||
|
err := fmt.Errorf(
|
||||||
|
"ephemeral_node_inactivity_timeout (%s) is set too low, must be more than %s",
|
||||||
|
viper.GetString("ephemeral_node_inactivity_timeout"),
|
||||||
|
minInactivityTimeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := headscale.NewHeadscale(cfg)
|
cfg := getHeadscaleConfig()
|
||||||
|
|
||||||
|
cfg.OIDC.MatchMap = loadOIDCMatchMap()
|
||||||
|
|
||||||
|
app, err := headscale.NewHeadscale(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -260,7 +311,7 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
|
|||||||
|
|
||||||
if viper.GetString("acl_policy_path") != "" {
|
if viper.GetString("acl_policy_path") != "" {
|
||||||
aclPath := absPath(viper.GetString("acl_policy_path"))
|
aclPath := absPath(viper.GetString("acl_policy_path"))
|
||||||
err = h.LoadACLPolicy(aclPath)
|
err = app.LoadACLPolicy(aclPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("path", aclPath).
|
Str("path", aclPath).
|
||||||
@ -269,46 +320,150 @@ func getHeadscaleApp() (*headscale.Headscale, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return h, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func JsonOutput(result interface{}, errResult error, outputFormat string) {
|
func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.ClientConn, context.CancelFunc) {
|
||||||
|
cfg := getHeadscaleConfig()
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Dur("timeout", cfg.CLI.Timeout).
|
||||||
|
Msgf("Setting timeout")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), cfg.CLI.Timeout)
|
||||||
|
|
||||||
|
grpcOptions := []grpc.DialOption{
|
||||||
|
grpc.WithBlock(),
|
||||||
|
}
|
||||||
|
|
||||||
|
address := cfg.CLI.Address
|
||||||
|
|
||||||
|
// If the address is not set, we assume that we are on the server hosting headscale.
|
||||||
|
if address == "" {
|
||||||
|
log.Debug().
|
||||||
|
Str("socket", cfg.UnixSocket).
|
||||||
|
Msgf("HEADSCALE_CLI_ADDRESS environment is not set, connecting to unix socket.")
|
||||||
|
|
||||||
|
address = cfg.UnixSocket
|
||||||
|
|
||||||
|
grpcOptions = append(
|
||||||
|
grpcOptions,
|
||||||
|
grpc.WithInsecure(),
|
||||||
|
grpc.WithContextDialer(headscale.GrpcSocketDialer),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// If we are not connecting to a local server, require an API key for authentication
|
||||||
|
apiKey := cfg.CLI.APIKey
|
||||||
|
if apiKey == "" {
|
||||||
|
log.Fatal().Msgf("HEADSCALE_CLI_API_KEY environment variable needs to be set.")
|
||||||
|
}
|
||||||
|
grpcOptions = append(grpcOptions,
|
||||||
|
grpc.WithPerRPCCredentials(tokenAuth{
|
||||||
|
token: apiKey,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if cfg.CLI.Insecure {
|
||||||
|
grpcOptions = append(grpcOptions, grpc.WithInsecure())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace().Caller().Str("address", address).Msg("Connecting via gRPC")
|
||||||
|
conn, err := grpc.DialContext(ctx, address, grpcOptions...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msgf("Could not connect: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := v1.NewHeadscaleServiceClient(conn)
|
||||||
|
|
||||||
|
return ctx, client, conn, cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func SuccessOutput(result interface{}, override string, outputFormat string) {
|
||||||
var j []byte
|
var j []byte
|
||||||
var err error
|
var err error
|
||||||
switch outputFormat {
|
switch outputFormat {
|
||||||
case "json":
|
case "json":
|
||||||
if errResult != nil {
|
j, err = json.MarshalIndent(result, "", "\t")
|
||||||
j, err = json.MarshalIndent(ErrorOutput{errResult.Error()}, "", "\t")
|
if err != nil {
|
||||||
if err != nil {
|
log.Fatal().Err(err)
|
||||||
log.Fatal().Err(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
j, err = json.MarshalIndent(result, "", "\t")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case "json-line":
|
case "json-line":
|
||||||
if errResult != nil {
|
j, err = json.Marshal(result)
|
||||||
j, err = json.Marshal(ErrorOutput{errResult.Error()})
|
if err != nil {
|
||||||
if err != nil {
|
log.Fatal().Err(err)
|
||||||
log.Fatal().Err(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
j, err = json.Marshal(result)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
case "yaml":
|
||||||
|
j, err = yaml.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
//nolint
|
||||||
|
fmt.Println(override)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint
|
||||||
fmt.Println(string(j))
|
fmt.Println(string(j))
|
||||||
}
|
}
|
||||||
|
|
||||||
func HasJsonOutputFlag() bool {
|
func ErrorOutput(errResult error, override string, outputFormat string) {
|
||||||
|
type errOutput struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessOutput(errOutput{errResult.Error()}, override, outputFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HasMachineOutputFlag() bool {
|
||||||
for _, arg := range os.Args {
|
for _, arg := range os.Args {
|
||||||
if arg == "json" || arg == "json-line" {
|
if arg == "json" || arg == "json-line" || arg == "yaml" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tokenAuth struct {
|
||||||
|
token string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return value is mapped to request headers.
|
||||||
|
func (t tokenAuth) GetRequestMetadata(
|
||||||
|
ctx context.Context,
|
||||||
|
in ...string,
|
||||||
|
) (map[string]string, error) {
|
||||||
|
return map[string]string{
|
||||||
|
"authorization": "Bearer " + t.token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tokenAuth) RequireTransportSecurity() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadOIDCMatchMap is a wrapper around viper to verifies that the keys in
|
||||||
|
// the match map is valid regex strings.
|
||||||
|
func loadOIDCMatchMap() map[string]string {
|
||||||
|
strMap := viper.GetStringMapString("oidc.domain_map")
|
||||||
|
|
||||||
|
for oidcMatcher := range strMap {
|
||||||
|
_ = regexp.MustCompile(oidcMatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFileMode(key string) fs.FileMode {
|
||||||
|
modeStr := viper.GetString(key)
|
||||||
|
|
||||||
|
mode, err := strconv.ParseUint(modeStr, headscale.Base8, headscale.BitSize64)
|
||||||
|
if err != nil {
|
||||||
|
return PermissionFallback
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.FileMode(mode)
|
||||||
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,11 +15,7 @@ var versionCmd = &cobra.Command{
|
|||||||
Short: "Print the version.",
|
Short: "Print the version.",
|
||||||
Long: "The version of headscale.",
|
Long: "The version of headscale.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
o, _ := cmd.Flags().GetString("output")
|
output, _ := cmd.Flags().GetString("output")
|
||||||
if strings.HasPrefix(o, "json") {
|
SuccessOutput(map[string]string{"version": Version}, Version, output)
|
||||||
JsonOutput(map[string]string{"version": Version}, nil, o)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Println(Version)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ func main() {
|
|||||||
colors = true
|
colors = true
|
||||||
case termcolor.LevelBasic:
|
case termcolor.LevelBasic:
|
||||||
colors = true
|
colors = true
|
||||||
|
case termcolor.LevelNone:
|
||||||
|
colors = false
|
||||||
default:
|
default:
|
||||||
// no color, return text as is.
|
// no color, return text as is.
|
||||||
colors = false
|
colors = false
|
||||||
@ -41,38 +43,41 @@ func main() {
|
|||||||
NoColor: !colors,
|
NoColor: !colors,
|
||||||
})
|
})
|
||||||
|
|
||||||
err := cli.LoadConfig("")
|
if err := cli.LoadConfig(""); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
machineOutput := cli.HasMachineOutputFlag()
|
||||||
|
|
||||||
logLevel := viper.GetString("log_level")
|
logLevel := viper.GetString("log_level")
|
||||||
switch logLevel {
|
level, err := zerolog.ParseLevel(logLevel)
|
||||||
case "trace":
|
if err != nil {
|
||||||
zerolog.SetGlobalLevel(zerolog.TraceLevel)
|
|
||||||
case "debug":
|
|
||||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
|
||||||
case "info":
|
|
||||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
|
||||||
case "warn":
|
|
||||||
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
|
||||||
case "error":
|
|
||||||
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
|
||||||
default:
|
|
||||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||||
|
} else {
|
||||||
|
zerolog.SetGlobalLevel(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonOutput := cli.HasJsonOutputFlag()
|
// If the user has requested a "machine" readable format,
|
||||||
if !viper.GetBool("disable_check_updates") && !jsonOutput {
|
// then disable login so the output remains valid.
|
||||||
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") && cli.Version != "dev" {
|
if machineOutput {
|
||||||
|
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !viper.GetBool("disable_check_updates") && !machineOutput {
|
||||||
|
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
|
||||||
|
cli.Version != "dev" {
|
||||||
githubTag := &latest.GithubTag{
|
githubTag := &latest.GithubTag{
|
||||||
Owner: "juanfont",
|
Owner: "juanfont",
|
||||||
Repository: "headscale",
|
Repository: "headscale",
|
||||||
}
|
}
|
||||||
res, err := latest.Check(githubTag, cli.Version)
|
res, err := latest.Check(githubTag, cli.Version)
|
||||||
if err == nil && res.Outdated {
|
if err == nil && res.Outdated {
|
||||||
fmt.Printf("An updated version of Headscale has been found (%s vs. your current %s). Check it out https://github.com/juanfont/headscale/releases\n",
|
//nolint
|
||||||
res.Current, cli.Version)
|
fmt.Printf(
|
||||||
|
"An updated version of Headscale has been found (%s vs. your current %s). Check it out https://github.com/juanfont/headscale/releases\n",
|
||||||
|
res.Current,
|
||||||
|
cli.Version,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -40,7 +40,10 @@ func (*Suite) TestConfigLoading(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Symlink the example config file
|
// Symlink the example config file
|
||||||
err = os.Symlink(filepath.Clean(path+"/../../config-example.yaml"), filepath.Join(tmpDir, "config.yaml"))
|
err = os.Symlink(
|
||||||
|
filepath.Clean(path+"/../../config-example.yaml"),
|
||||||
|
filepath.Join(tmpDir, "config.yaml"),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -52,13 +55,13 @@ func (*Suite) TestConfigLoading(c *check.C) {
|
|||||||
// Test that config file was interpreted correctly
|
// Test that config file was interpreted correctly
|
||||||
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
|
c.Assert(viper.GetString("server_url"), check.Equals, "http://127.0.0.1:8080")
|
||||||
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
|
c.Assert(viper.GetString("listen_addr"), check.Equals, "0.0.0.0:8080")
|
||||||
c.Assert(viper.GetStringSlice("derp.paths")[0], check.Equals, "derp-example.yaml")
|
|
||||||
c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3")
|
c.Assert(viper.GetString("db_type"), check.Equals, "sqlite3")
|
||||||
c.Assert(viper.GetString("db_path"), check.Equals, "db.sqlite")
|
c.Assert(viper.GetString("db_path"), check.Equals, "/var/lib/headscale/db.sqlite")
|
||||||
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
|
c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "")
|
||||||
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
|
c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http")
|
||||||
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
|
c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01")
|
||||||
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
|
c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1")
|
||||||
|
c.Assert(cli.GetFileMode("unix_socket_permission"), check.Equals, fs.FileMode(0o770))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Suite) TestDNSConfigLoading(c *check.C) {
|
func (*Suite) TestDNSConfigLoading(c *check.C) {
|
||||||
@ -74,7 +77,10 @@ func (*Suite) TestDNSConfigLoading(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Symlink the example config file
|
// Symlink the example config file
|
||||||
err = os.Symlink(filepath.Clean(path+"/../../config-example.yaml"), filepath.Join(tmpDir, "config.yaml"))
|
err = os.Symlink(
|
||||||
|
filepath.Clean(path+"/../../config-example.yaml"),
|
||||||
|
filepath.Join(tmpDir, "config.yaml"),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -94,7 +100,7 @@ func (*Suite) TestDNSConfigLoading(c *check.C) {
|
|||||||
func writeConfig(c *check.C, tmpDir string, configYaml []byte) {
|
func writeConfig(c *check.C, tmpDir string, configYaml []byte) {
|
||||||
// Populate a custom config file
|
// Populate a custom config file
|
||||||
configFile := filepath.Join(tmpDir, "config.yaml")
|
configFile := filepath.Join(tmpDir, "config.yaml")
|
||||||
err := ioutil.WriteFile(configFile, configYaml, 0o644)
|
err := ioutil.WriteFile(configFile, configYaml, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("Couldn't write file %s", configFile)
|
c.Fatalf("Couldn't write file %s", configFile)
|
||||||
}
|
}
|
||||||
@ -106,7 +112,6 @@ func (*Suite) TestTLSConfigValidation(c *check.C) {
|
|||||||
c.Fatal(err)
|
c.Fatal(err)
|
||||||
}
|
}
|
||||||
// defer os.RemoveAll(tmpDir)
|
// defer os.RemoveAll(tmpDir)
|
||||||
fmt.Println(tmpDir)
|
|
||||||
|
|
||||||
configYaml := []byte(
|
configYaml := []byte(
|
||||||
"---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"",
|
"---\ntls_letsencrypt_hostname: \"example.com\"\ntls_letsencrypt_challenge_type: \"\"\ntls_cert_path: \"abc.pem\"",
|
||||||
@ -128,8 +133,11 @@ func (*Suite) TestTLSConfigValidation(c *check.C) {
|
|||||||
check.Matches,
|
check.Matches,
|
||||||
".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*",
|
".*Fatal config error: the only supported values for tls_letsencrypt_challenge_type are.*",
|
||||||
)
|
)
|
||||||
c.Assert(tmp, check.Matches, ".*Fatal config error: server_url must start with https:// or http://.*")
|
c.Assert(
|
||||||
fmt.Println(tmp)
|
tmp,
|
||||||
|
check.Matches,
|
||||||
|
".*Fatal config error: server_url must start with https:// or http://.*",
|
||||||
|
)
|
||||||
|
|
||||||
// Check configuration validation errors (2)
|
// Check configuration validation errors (2)
|
||||||
configYaml = []byte(
|
configYaml = []byte(
|
||||||
|
@ -1,38 +1,65 @@
|
|||||||
---
|
---
|
||||||
|
# headscale will look for a configuration file named `config.yaml` (or `config.json`) in the following order:
|
||||||
|
#
|
||||||
|
# - `/etc/headscale`
|
||||||
|
# - `~/.headscale`
|
||||||
|
# - current working directory
|
||||||
|
|
||||||
# The url clients will connect to.
|
# The url clients will connect to.
|
||||||
# Typically this will be a domain.
|
# Typically this will be a domain like:
|
||||||
|
#
|
||||||
|
# https://myheadscale.example.com:443
|
||||||
|
#
|
||||||
server_url: http://127.0.0.1:8080
|
server_url: http://127.0.0.1:8080
|
||||||
|
|
||||||
# Address to listen to / bind to on the server
|
# Address to listen to / bind to on the server
|
||||||
|
#
|
||||||
listen_addr: 0.0.0.0:8080
|
listen_addr: 0.0.0.0:8080
|
||||||
|
|
||||||
# Path to WireGuard private key file
|
# Private key used encrypt the traffic between headscale
|
||||||
private_key_path: private.key
|
# and Tailscale clients.
|
||||||
|
# The private key file which will be
|
||||||
|
# autogenerated if it's missing
|
||||||
|
private_key_path: /var/lib/headscale/private.key
|
||||||
|
|
||||||
|
# DERP is a relay system that Tailscale uses when a direct
|
||||||
|
# connection cannot be established.
|
||||||
|
# https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp
|
||||||
|
#
|
||||||
|
# headscale needs a list of DERP servers that can be presented
|
||||||
|
# to the clients.
|
||||||
derp:
|
derp:
|
||||||
# List of externally available DERP maps encoded in JSON
|
# List of externally available DERP maps encoded in JSON
|
||||||
urls:
|
urls:
|
||||||
- https://controlplane.tailscale.com/derpmap/default
|
- https://controlplane.tailscale.com/derpmap/default
|
||||||
|
|
||||||
# Locally available DERP map files encoded in YAML
|
# Locally available DERP map files encoded in YAML
|
||||||
paths:
|
#
|
||||||
- derp-example.yaml
|
# This option is mostly interesting for people hosting
|
||||||
|
# their own DERP servers:
|
||||||
|
# https://tailscale.com/kb/1118/custom-derp-servers/
|
||||||
|
#
|
||||||
|
# paths:
|
||||||
|
# - /etc/headscale/derp-example.yaml
|
||||||
|
paths: []
|
||||||
|
|
||||||
# If enabled, a worker will be set up to periodically
|
# If enabled, a worker will be set up to periodically
|
||||||
# refresh the given sources and update the derpmap
|
# refresh the given sources and update the derpmap
|
||||||
# will be set up.
|
# will be set up.
|
||||||
auto_update_enabled: true
|
auto_update_enabled: true
|
||||||
|
|
||||||
# How often should we check for updates?
|
# How often should we check for DERP updates?
|
||||||
update_frequency: 24h
|
update_frequency: 24h
|
||||||
|
|
||||||
# Disables the automatic check for updates on startup
|
# Disables the automatic check for headscale updates on startup
|
||||||
disable_check_updates: false
|
disable_check_updates: false
|
||||||
|
|
||||||
|
# Time before an inactive ephemeral node is deleted?
|
||||||
ephemeral_node_inactivity_timeout: 30m
|
ephemeral_node_inactivity_timeout: 30m
|
||||||
|
|
||||||
# SQLite config
|
# SQLite config
|
||||||
db_type: sqlite3
|
db_type: sqlite3
|
||||||
db_path: db.sqlite
|
db_path: /var/lib/headscale/db.sqlite
|
||||||
|
|
||||||
# # Postgres config
|
# # Postgres config
|
||||||
# db_type: postgres
|
# db_type: postgres
|
||||||
@ -42,25 +69,98 @@ db_path: db.sqlite
|
|||||||
# db_user: foo
|
# db_user: foo
|
||||||
# db_pass: bar
|
# db_pass: bar
|
||||||
|
|
||||||
|
### TLS configuration
|
||||||
|
#
|
||||||
|
## Let's encrypt / ACME
|
||||||
|
#
|
||||||
|
# headscale supports automatically requesting and setting up
|
||||||
|
# TLS for a domain with Let's Encrypt.
|
||||||
|
#
|
||||||
|
# URL to ACME directory
|
||||||
acme_url: https://acme-v02.api.letsencrypt.org/directory
|
acme_url: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
|
||||||
|
# Email to register with ACME provider
|
||||||
acme_email: ""
|
acme_email: ""
|
||||||
|
|
||||||
|
# Domain name to request a TLS certificate for:
|
||||||
tls_letsencrypt_hostname: ""
|
tls_letsencrypt_hostname: ""
|
||||||
tls_letsencrypt_listen: ":http"
|
|
||||||
tls_letsencrypt_cache_dir: ".cache"
|
|
||||||
tls_letsencrypt_challenge_type: HTTP-01
|
|
||||||
|
|
||||||
|
# Path to store certificates and metadata needed by
|
||||||
|
# letsencrypt
|
||||||
|
tls_letsencrypt_cache_dir: /var/lib/headscale/cache
|
||||||
|
|
||||||
|
# Type of ACME challenge to use, currently supported types:
|
||||||
|
# HTTP-01 or TLS_ALPN-01
|
||||||
|
# See [docs/tls.md](docs/tls.md) for more information
|
||||||
|
tls_letsencrypt_challenge_type: HTTP-01
|
||||||
|
# When HTTP-01 challenge is chosen, letsencrypt must set up a
|
||||||
|
# verification endpoint, and it will be listning on:
|
||||||
|
# :http = port 80
|
||||||
|
tls_letsencrypt_listen: ":http"
|
||||||
|
|
||||||
|
## Use already defined certificates:
|
||||||
tls_cert_path: ""
|
tls_cert_path: ""
|
||||||
tls_key_path: ""
|
tls_key_path: ""
|
||||||
|
|
||||||
|
log_level: info
|
||||||
|
|
||||||
# Path to a file containg ACL policies.
|
# Path to a file containg ACL policies.
|
||||||
|
# Recommended path: /etc/headscale/acl.hujson
|
||||||
acl_policy_path: ""
|
acl_policy_path: ""
|
||||||
|
|
||||||
|
## DNS
|
||||||
|
#
|
||||||
|
# headscale supports Tailscale's DNS configuration and MagicDNS.
|
||||||
|
# Please have a look to their KB to better understand the concepts:
|
||||||
|
#
|
||||||
|
# - https://tailscale.com/kb/1054/dns/
|
||||||
|
# - https://tailscale.com/kb/1081/magicdns/
|
||||||
|
# - https://tailscale.com/blog/2021-09-private-dns-with-magicdns/
|
||||||
|
#
|
||||||
dns_config:
|
dns_config:
|
||||||
# Upstream DNS servers
|
# List of DNS servers to expose to clients.
|
||||||
nameservers:
|
nameservers:
|
||||||
- 1.1.1.1
|
- 1.1.1.1
|
||||||
|
|
||||||
|
# Split DNS (see https://tailscale.com/kb/1054/dns/),
|
||||||
|
# list of search domains and the DNS to query for each one.
|
||||||
|
#
|
||||||
|
# restricted_nameservers:
|
||||||
|
# foo.bar.com:
|
||||||
|
# - 1.1.1.1
|
||||||
|
# darp.headscale.net:
|
||||||
|
# - 1.1.1.1
|
||||||
|
# - 8.8.8.8
|
||||||
|
|
||||||
|
# Search domains to inject.
|
||||||
domains: []
|
domains: []
|
||||||
|
|
||||||
|
# Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/).
|
||||||
|
# Only works if there is at least a nameserver defined.
|
||||||
magic_dns: true
|
magic_dns: true
|
||||||
|
|
||||||
|
# Defines the base domain to create the hostnames for MagicDNS.
|
||||||
|
# `base_domain` must be a FQDNs, without the trailing dot.
|
||||||
|
# The FQDN of the hosts will be
|
||||||
|
# `hostname.namespace.base_domain` (e.g., _myhost.mynamespace.example.com_).
|
||||||
base_domain: example.com
|
base_domain: example.com
|
||||||
|
|
||||||
|
# Unix socket used for the CLI to connect without authentication
|
||||||
|
# Note: for local development, you probably want to change this to:
|
||||||
|
# unix_socket: ./headscale.sock
|
||||||
|
unix_socket: /var/run/headscale.sock
|
||||||
|
unix_socket_permission: "0770"
|
||||||
|
#
|
||||||
|
# headscale supports experimental OpenID connect support,
|
||||||
|
# it is still being tested and might have some bugs, please
|
||||||
|
# help us test it.
|
||||||
|
# OpenID Connect
|
||||||
|
# oidc:
|
||||||
|
# issuer: "https://your-oidc.issuer.com/path"
|
||||||
|
# client_id: "your-oidc-client-id"
|
||||||
|
# client_secret: "your-oidc-client-secret"
|
||||||
|
#
|
||||||
|
# # Domain map is used to map incomming users (by their email) to
|
||||||
|
# # a namespace. The key can be a string, or regex.
|
||||||
|
# domain_map:
|
||||||
|
# ".*": default-namespace
|
||||||
|
35
db.go
35
db.go
@ -9,7 +9,10 @@ import (
|
|||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dbVersion = "1"
|
const (
|
||||||
|
dbVersion = "1"
|
||||||
|
errValueNotFound = Error("not found")
|
||||||
|
)
|
||||||
|
|
||||||
// KV is a key-value store in a psql table. For future use...
|
// KV is a key-value store in a psql table. For future use...
|
||||||
type KV struct {
|
type KV struct {
|
||||||
@ -24,7 +27,7 @@ func (h *Headscale) initDB() error {
|
|||||||
}
|
}
|
||||||
h.db = db
|
h.db = db
|
||||||
|
|
||||||
if h.dbType == "postgres" {
|
if h.dbType == Postgres {
|
||||||
db.Exec("create extension if not exists \"uuid-ossp\";")
|
db.Exec("create extension if not exists \"uuid-ossp\";")
|
||||||
}
|
}
|
||||||
err = db.AutoMigrate(&Machine{})
|
err = db.AutoMigrate(&Machine{})
|
||||||
@ -50,6 +53,7 @@ func (h *Headscale) initDB() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = h.setValue("db_version", dbVersion)
|
err = h.setValue("db_version", dbVersion)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,12 +69,12 @@ func (h *Headscale) openDB() (*gorm.DB, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch h.dbType {
|
switch h.dbType {
|
||||||
case "sqlite3":
|
case Sqlite:
|
||||||
db, err = gorm.Open(sqlite.Open(h.dbString), &gorm.Config{
|
db, err = gorm.Open(sqlite.Open(h.dbString), &gorm.Config{
|
||||||
DisableForeignKeyConstraintWhenMigrating: true,
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
Logger: log,
|
Logger: log,
|
||||||
})
|
})
|
||||||
case "postgres":
|
case Postgres:
|
||||||
db, err = gorm.Open(postgres.Open(h.dbString), &gorm.Config{
|
db, err = gorm.Open(postgres.Open(h.dbString), &gorm.Config{
|
||||||
DisableForeignKeyConstraintWhenMigrating: true,
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
Logger: log,
|
Logger: log,
|
||||||
@ -84,28 +88,33 @@ func (h *Headscale) openDB() (*gorm.DB, error) {
|
|||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getValue returns the value for the given key in KV
|
// getValue returns the value for the given key in KV.
|
||||||
func (h *Headscale) getValue(key string) (string, error) {
|
func (h *Headscale) getValue(key string) (string, error) {
|
||||||
var row KV
|
var row KV
|
||||||
if result := h.db.First(&row, "key = ?", key); errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
if result := h.db.First(&row, "key = ?", key); errors.Is(
|
||||||
return "", errors.New("not found")
|
result.Error,
|
||||||
|
gorm.ErrRecordNotFound,
|
||||||
|
) {
|
||||||
|
return "", errValueNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return row.Value, nil
|
return row.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setValue sets value for the given key in KV
|
// setValue sets value for the given key in KV.
|
||||||
func (h *Headscale) setValue(key string, value string) error {
|
func (h *Headscale) setValue(key string, value string) error {
|
||||||
kv := KV{
|
keyValue := KV{
|
||||||
Key: key,
|
Key: key,
|
||||||
Value: value,
|
Value: value,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := h.getValue(key)
|
if _, err := h.getValue(key); err == nil {
|
||||||
if err == nil {
|
h.db.Model(&keyValue).Where("key = ?", key).Update("value", value)
|
||||||
h.db.Model(&kv).Where("key = ?", key).Update("value", value)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
h.db.Create(kv)
|
h.db.Create(keyValue)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
# If you plan to somehow use headscale, please deploy your own DERP infra: https://tailscale.com/kb/1118/custom-derp-servers/
|
# If you plan to somehow use headscale, please deploy your own DERP infra: https://tailscale.com/kb/1118/custom-derp-servers/
|
||||||
regions:
|
regions:
|
||||||
900:
|
900:
|
||||||
regionid: 900
|
regionid: 900
|
||||||
regioncode: custom
|
regioncode: custom
|
||||||
regionname: My Region
|
regionname: My Region
|
||||||
nodes:
|
nodes:
|
||||||
- name: 1a
|
- name: 900a
|
||||||
regionid: 1
|
regionid: 900
|
||||||
hostname: myderp.mydomain.no
|
hostname: myderp.mydomain.no
|
||||||
ipv4: 123.123.123.123
|
ipv4: 123.123.123.123
|
||||||
ipv6: "2604:a880:400:d1::828:b001"
|
ipv6: "2604:a880:400:d1::828:b001"
|
||||||
stunport: 0
|
stunport: 0
|
||||||
stunonly: false
|
stunonly: false
|
||||||
derptestport: 0
|
derptestport: 0
|
||||||
|
26
derp.go
26
derp.go
@ -1,6 +1,7 @@
|
|||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -10,9 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,14 +27,24 @@ func loadDERPMapFromPath(path string) (*tailcfg.DERPMap, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = yaml.Unmarshal(b, &derpMap)
|
err = yaml.Unmarshal(b, &derpMap)
|
||||||
|
|
||||||
return &derpMap, err
|
return &derpMap, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDERPMapFromURL(addr url.URL) (*tailcfg.DERPMap, error) {
|
func loadDERPMapFromURL(addr url.URL) (*tailcfg.DERPMap, error) {
|
||||||
client := http.Client{
|
ctx, cancel := context.WithTimeout(context.Background(), HTTPReadTimeout)
|
||||||
Timeout: 10 * time.Second,
|
defer cancel()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", addr.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
resp, err := client.Get(addr.String())
|
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: HTTPReadTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -48,6 +57,7 @@ func loadDERPMapFromURL(addr url.URL) (*tailcfg.DERPMap, error) {
|
|||||||
|
|
||||||
var derpMap tailcfg.DERPMap
|
var derpMap tailcfg.DERPMap
|
||||||
err = json.Unmarshal(body, &derpMap)
|
err = json.Unmarshal(body, &derpMap)
|
||||||
|
|
||||||
return &derpMap, err
|
return &derpMap, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +65,7 @@ func loadDERPMapFromURL(addr url.URL) (*tailcfg.DERPMap, error) {
|
|||||||
// DERPMap, it will _only_ look at the Regions, an integer.
|
// DERPMap, it will _only_ look at the Regions, an integer.
|
||||||
// If a region exists in two of the given DERPMaps, the region
|
// If a region exists in two of the given DERPMaps, the region
|
||||||
// form the _last_ DERPMap will be preserved.
|
// form the _last_ DERPMap will be preserved.
|
||||||
// An empty DERPMap list will result in a DERPMap with no regions
|
// An empty DERPMap list will result in a DERPMap with no regions.
|
||||||
func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap {
|
func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap {
|
||||||
result := tailcfg.DERPMap{
|
result := tailcfg.DERPMap{
|
||||||
OmitDefaultRegions: false,
|
OmitDefaultRegions: false,
|
||||||
@ -86,6 +96,7 @@ func GetDERPMap(cfg DERPConfig) *tailcfg.DERPMap {
|
|||||||
Str("path", path).
|
Str("path", path).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not load DERP map from path")
|
Msg("Could not load DERP map from path")
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +115,7 @@ func GetDERPMap(cfg DERPConfig) *tailcfg.DERPMap {
|
|||||||
Str("url", addr.String()).
|
Str("url", addr.String()).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("Could not load DERP map from path")
|
Msg("Could not load DERP map from path")
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +156,7 @@ func (h *Headscale) scheduledDERPMapUpdateWorker(cancelChan <-chan struct{}) {
|
|||||||
Msg("Failed to fetch namespaces")
|
Msg("Failed to fetch namespaces")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, namespace := range *namespaces {
|
for _, namespace := range namespaces {
|
||||||
h.setLastStateChangeToNow(namespace.Name)
|
h.setLastStateChangeToNow(namespace.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
34
dns.go
34
dns.go
@ -10,6 +10,10 @@ import (
|
|||||||
"tailscale.com/util/dnsname"
|
"tailscale.com/util/dnsname"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ByteSize = 8
|
||||||
|
)
|
||||||
|
|
||||||
// generateMagicDNSRootDomains generates a list of DNS entries to be included in `Routes` in `MapResponse`.
|
// generateMagicDNSRootDomains generates a list of DNS entries to be included in `Routes` in `MapResponse`.
|
||||||
// This list of reverse DNS entries instructs the OS on what subnets and domains the Tailscale embedded DNS
|
// This list of reverse DNS entries instructs the OS on what subnets and domains the Tailscale embedded DNS
|
||||||
// server (listening in 100.100.100.100 udp/53) should be used for.
|
// server (listening in 100.100.100.100 udp/53) should be used for.
|
||||||
@ -30,7 +34,9 @@ import (
|
|||||||
|
|
||||||
// From the netmask we can find out the wildcard bits (the bits that are not set in the netmask).
|
// From the netmask we can find out the wildcard bits (the bits that are not set in the netmask).
|
||||||
// This allows us to then calculate the subnets included in the subsequent class block and generate the entries.
|
// This allows us to then calculate the subnets included in the subsequent class block and generate the entries.
|
||||||
func generateMagicDNSRootDomains(ipPrefix netaddr.IPPrefix, baseDomain string) ([]dnsname.FQDN, error) {
|
func generateMagicDNSRootDomains(
|
||||||
|
ipPrefix netaddr.IPPrefix,
|
||||||
|
) []dnsname.FQDN {
|
||||||
// TODO(juanfont): we are not handing out IPv6 addresses yet
|
// TODO(juanfont): we are not handing out IPv6 addresses yet
|
||||||
// and in fact this is Tailscale.com's range (note the fd7a:115c:a1e0: range in the fc00::/7 network)
|
// and in fact this is Tailscale.com's range (note the fd7a:115c:a1e0: range in the fc00::/7 network)
|
||||||
ipv6base := dnsname.FQDN("0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.")
|
ipv6base := dnsname.FQDN("0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa.")
|
||||||
@ -41,15 +47,15 @@ func generateMagicDNSRootDomains(ipPrefix netaddr.IPPrefix, baseDomain string) (
|
|||||||
maskBits, _ := netRange.Mask.Size()
|
maskBits, _ := netRange.Mask.Size()
|
||||||
|
|
||||||
// lastOctet is the last IP byte covered by the mask
|
// lastOctet is the last IP byte covered by the mask
|
||||||
lastOctet := maskBits / 8
|
lastOctet := maskBits / ByteSize
|
||||||
|
|
||||||
// wildcardBits is the number of bits not under the mask in the lastOctet
|
// wildcardBits is the number of bits not under the mask in the lastOctet
|
||||||
wildcardBits := 8 - maskBits%8
|
wildcardBits := ByteSize - maskBits%ByteSize
|
||||||
|
|
||||||
// min is the value in the lastOctet byte of the IP
|
// min is the value in the lastOctet byte of the IP
|
||||||
// max is basically 2^wildcardBits - i.e., the value when all the wildcardBits are set to 1
|
// max is basically 2^wildcardBits - i.e., the value when all the wildcardBits are set to 1
|
||||||
min := uint(netRange.IP[lastOctet])
|
min := uint(netRange.IP[lastOctet])
|
||||||
max := uint((min + 1<<uint(wildcardBits)) - 1)
|
max := (min + 1<<uint(wildcardBits)) - 1
|
||||||
|
|
||||||
// here we generate the base domain (e.g., 100.in-addr.arpa., 16.172.in-addr.arpa., etc.)
|
// here we generate the base domain (e.g., 100.in-addr.arpa., 16.172.in-addr.arpa., etc.)
|
||||||
rdnsSlice := []string{}
|
rdnsSlice := []string{}
|
||||||
@ -66,18 +72,27 @@ func generateMagicDNSRootDomains(ipPrefix netaddr.IPPrefix, baseDomain string) (
|
|||||||
}
|
}
|
||||||
fqdns = append(fqdns, fqdn)
|
fqdns = append(fqdns, fqdn)
|
||||||
}
|
}
|
||||||
return fqdns, nil
|
|
||||||
|
return fqdns
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMapResponseDNSConfig(dnsConfigOrig *tailcfg.DNSConfig, baseDomain string, m Machine, peers Machines) (*tailcfg.DNSConfig, error) {
|
func getMapResponseDNSConfig(
|
||||||
|
dnsConfigOrig *tailcfg.DNSConfig,
|
||||||
|
baseDomain string,
|
||||||
|
machine Machine,
|
||||||
|
peers Machines,
|
||||||
|
) *tailcfg.DNSConfig {
|
||||||
var dnsConfig *tailcfg.DNSConfig
|
var dnsConfig *tailcfg.DNSConfig
|
||||||
if dnsConfigOrig != nil && dnsConfigOrig.Proxied { // if MagicDNS is enabled
|
if dnsConfigOrig != nil && dnsConfigOrig.Proxied { // if MagicDNS is enabled
|
||||||
// Only inject the Search Domain of the current namespace - shared nodes should use their full FQDN
|
// Only inject the Search Domain of the current namespace - shared nodes should use their full FQDN
|
||||||
dnsConfig = dnsConfigOrig.Clone()
|
dnsConfig = dnsConfigOrig.Clone()
|
||||||
dnsConfig.Domains = append(dnsConfig.Domains, fmt.Sprintf("%s.%s", m.Namespace.Name, baseDomain))
|
dnsConfig.Domains = append(
|
||||||
|
dnsConfig.Domains,
|
||||||
|
fmt.Sprintf("%s.%s", machine.Namespace.Name, baseDomain),
|
||||||
|
)
|
||||||
|
|
||||||
namespaceSet := set.New(set.ThreadSafe)
|
namespaceSet := set.New(set.ThreadSafe)
|
||||||
namespaceSet.Add(m.Namespace)
|
namespaceSet.Add(machine.Namespace)
|
||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
namespaceSet.Add(p.Namespace)
|
namespaceSet.Add(p.Namespace)
|
||||||
}
|
}
|
||||||
@ -88,5 +103,6 @@ func getMapResponseDNSConfig(dnsConfigOrig *tailcfg.DNSConfig, baseDomain string
|
|||||||
} else {
|
} else {
|
||||||
dnsConfig = dnsConfigOrig
|
dnsConfig = dnsConfigOrig
|
||||||
}
|
}
|
||||||
return dnsConfig, nil
|
|
||||||
|
return dnsConfig
|
||||||
}
|
}
|
||||||
|
227
dns_test.go
227
dns_test.go
@ -11,13 +11,13 @@ import (
|
|||||||
|
|
||||||
func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
|
func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
|
||||||
prefix := netaddr.MustParseIPPrefix("100.64.0.0/10")
|
prefix := netaddr.MustParseIPPrefix("100.64.0.0/10")
|
||||||
domains, err := generateMagicDNSRootDomains(prefix, "foobar.headscale.net")
|
domains := generateMagicDNSRootDomains(prefix)
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
if domain == "64.100.in-addr.arpa." {
|
if domain == "64.100.in-addr.arpa." {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,6 +27,7 @@ func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
|
|||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
if domain == "100.100.in-addr.arpa." {
|
if domain == "100.100.in-addr.arpa." {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,6 +37,7 @@ func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
|
|||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
if domain == "127.100.in-addr.arpa." {
|
if domain == "127.100.in-addr.arpa." {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,13 +46,13 @@ func (s *Suite) TestMagicDNSRootDomains100(c *check.C) {
|
|||||||
|
|
||||||
func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
|
func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
|
||||||
prefix := netaddr.MustParseIPPrefix("172.16.0.0/16")
|
prefix := netaddr.MustParseIPPrefix("172.16.0.0/16")
|
||||||
domains, err := generateMagicDNSRootDomains(prefix, "headscale.net")
|
domains := generateMagicDNSRootDomains(prefix)
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
if domain == "0.16.172.in-addr.arpa." {
|
if domain == "0.16.172.in-addr.arpa." {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +62,7 @@ func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
|
|||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
if domain == "255.16.172.in-addr.arpa." {
|
if domain == "255.16.172.in-addr.arpa." {
|
||||||
found = true
|
found = true
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,100 +70,120 @@ func (s *Suite) TestMagicDNSRootDomains172(c *check.C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
||||||
n1, err := h.CreateNamespace("shared1")
|
namespaceShared1, err := app.CreateNamespace("shared1")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
n2, err := h.CreateNamespace("shared2")
|
namespaceShared2, err := app.CreateNamespace("shared2")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
n3, err := h.CreateNamespace("shared3")
|
namespaceShared3, err := app.CreateNamespace("shared3")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak1n1, err := h.CreatePreAuthKey(n1.Name, false, false, nil)
|
preAuthKeyInShared1, err := app.CreatePreAuthKey(
|
||||||
|
namespaceShared1.Name,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak2n2, err := h.CreatePreAuthKey(n2.Name, false, false, nil)
|
preAuthKeyInShared2, err := app.CreatePreAuthKey(
|
||||||
|
namespaceShared2.Name,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak3n3, err := h.CreatePreAuthKey(n3.Name, false, false, nil)
|
preAuthKeyInShared3, err := app.CreatePreAuthKey(
|
||||||
|
namespaceShared3.Name,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak4n1, err := h.CreatePreAuthKey(n1.Name, false, false, nil)
|
PreAuthKey2InShared1, err := app.CreatePreAuthKey(
|
||||||
|
namespaceShared1.Name,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
|
_, err = app.GetMachine(namespaceShared1.Name, "test_get_shared_nodes_1")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
|
|
||||||
m1 := &Machine{
|
machineInShared1 := &Machine{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
MachineKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
MachineKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
||||||
NodeKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
NodeKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
||||||
DiscoKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
DiscoKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
||||||
Name: "test_get_shared_nodes_1",
|
Name: "test_get_shared_nodes_1",
|
||||||
NamespaceID: n1.ID,
|
NamespaceID: namespaceShared1.ID,
|
||||||
Namespace: *n1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: "authKey",
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.1",
|
IPAddress: "100.64.0.1",
|
||||||
AuthKeyID: uint(pak1n1.ID),
|
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
||||||
}
|
}
|
||||||
h.db.Save(m1)
|
app.db.Save(machineInShared1)
|
||||||
|
|
||||||
_, err = h.GetMachine(n1.Name, m1.Name)
|
_, err = app.GetMachine(namespaceShared1.Name, machineInShared1.Name)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
m2 := &Machine{
|
machineInShared2 := &Machine{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
Name: "test_get_shared_nodes_2",
|
Name: "test_get_shared_nodes_2",
|
||||||
NamespaceID: n2.ID,
|
NamespaceID: namespaceShared2.ID,
|
||||||
Namespace: *n2,
|
Namespace: *namespaceShared2,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: "authKey",
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.2",
|
IPAddress: "100.64.0.2",
|
||||||
AuthKeyID: uint(pak2n2.ID),
|
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
||||||
}
|
}
|
||||||
h.db.Save(m2)
|
app.db.Save(machineInShared2)
|
||||||
|
|
||||||
_, err = h.GetMachine(n2.Name, m2.Name)
|
_, err = app.GetMachine(namespaceShared2.Name, machineInShared2.Name)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
m3 := &Machine{
|
machineInShared3 := &Machine{
|
||||||
ID: 3,
|
ID: 3,
|
||||||
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
Name: "test_get_shared_nodes_3",
|
Name: "test_get_shared_nodes_3",
|
||||||
NamespaceID: n3.ID,
|
NamespaceID: namespaceShared3.ID,
|
||||||
Namespace: *n3,
|
Namespace: *namespaceShared3,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: "authKey",
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.3",
|
IPAddress: "100.64.0.3",
|
||||||
AuthKeyID: uint(pak3n3.ID),
|
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
||||||
}
|
}
|
||||||
h.db.Save(m3)
|
app.db.Save(machineInShared3)
|
||||||
|
|
||||||
_, err = h.GetMachine(n3.Name, m3.Name)
|
_, err = app.GetMachine(namespaceShared3.Name, machineInShared3.Name)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
m4 := &Machine{
|
machine2InShared1 := &Machine{
|
||||||
ID: 4,
|
ID: 4,
|
||||||
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
Name: "test_get_shared_nodes_4",
|
Name: "test_get_shared_nodes_4",
|
||||||
NamespaceID: n1.ID,
|
NamespaceID: namespaceShared1.ID,
|
||||||
Namespace: *n1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: "authKey",
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.4",
|
IPAddress: "100.64.0.4",
|
||||||
AuthKeyID: uint(pak4n1.ID),
|
AuthKeyID: uint(PreAuthKey2InShared1.ID),
|
||||||
}
|
}
|
||||||
h.db.Save(m4)
|
app.db.Save(machine2InShared1)
|
||||||
|
|
||||||
err = h.AddSharedMachineToNamespace(m2, n1)
|
err = app.AddSharedMachineToNamespace(machineInShared2, namespaceShared1)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
baseDomain := "foobar.headscale.net"
|
baseDomain := "foobar.headscale.net"
|
||||||
@ -170,122 +193,146 @@ func (s *Suite) TestDNSConfigMapResponseWithMagicDNS(c *check.C) {
|
|||||||
Proxied: true,
|
Proxied: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
m1peers, err := h.getPeers(m1)
|
peersOfMachineInShared1, err := app.getPeers(machineInShared1)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
dnsConfig, err := getMapResponseDNSConfig(&dnsConfigOrig, baseDomain, *m1, m1peers)
|
dnsConfig := getMapResponseDNSConfig(
|
||||||
c.Assert(err, check.IsNil)
|
&dnsConfigOrig,
|
||||||
|
baseDomain,
|
||||||
|
*machineInShared1,
|
||||||
|
peersOfMachineInShared1,
|
||||||
|
)
|
||||||
c.Assert(dnsConfig, check.NotNil)
|
c.Assert(dnsConfig, check.NotNil)
|
||||||
c.Assert(len(dnsConfig.Routes), check.Equals, 2)
|
c.Assert(len(dnsConfig.Routes), check.Equals, 2)
|
||||||
|
|
||||||
routeN1 := fmt.Sprintf("%s.%s", n1.Name, baseDomain)
|
domainRouteShared1 := fmt.Sprintf("%s.%s", namespaceShared1.Name, baseDomain)
|
||||||
_, ok := dnsConfig.Routes[routeN1]
|
_, ok := dnsConfig.Routes[domainRouteShared1]
|
||||||
c.Assert(ok, check.Equals, true)
|
c.Assert(ok, check.Equals, true)
|
||||||
|
|
||||||
routeN2 := fmt.Sprintf("%s.%s", n2.Name, baseDomain)
|
domainRouteShared2 := fmt.Sprintf("%s.%s", namespaceShared2.Name, baseDomain)
|
||||||
_, ok = dnsConfig.Routes[routeN2]
|
_, ok = dnsConfig.Routes[domainRouteShared2]
|
||||||
c.Assert(ok, check.Equals, true)
|
c.Assert(ok, check.Equals, true)
|
||||||
|
|
||||||
routeN3 := fmt.Sprintf("%s.%s", n3.Name, baseDomain)
|
domainRouteShared3 := fmt.Sprintf("%s.%s", namespaceShared3.Name, baseDomain)
|
||||||
_, ok = dnsConfig.Routes[routeN3]
|
_, ok = dnsConfig.Routes[domainRouteShared3]
|
||||||
c.Assert(ok, check.Equals, false)
|
c.Assert(ok, check.Equals, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
||||||
n1, err := h.CreateNamespace("shared1")
|
namespaceShared1, err := app.CreateNamespace("shared1")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
n2, err := h.CreateNamespace("shared2")
|
namespaceShared2, err := app.CreateNamespace("shared2")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
n3, err := h.CreateNamespace("shared3")
|
namespaceShared3, err := app.CreateNamespace("shared3")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak1n1, err := h.CreatePreAuthKey(n1.Name, false, false, nil)
|
preAuthKeyInShared1, err := app.CreatePreAuthKey(
|
||||||
|
namespaceShared1.Name,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak2n2, err := h.CreatePreAuthKey(n2.Name, false, false, nil)
|
preAuthKeyInShared2, err := app.CreatePreAuthKey(
|
||||||
|
namespaceShared2.Name,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak3n3, err := h.CreatePreAuthKey(n3.Name, false, false, nil)
|
preAuthKeyInShared3, err := app.CreatePreAuthKey(
|
||||||
|
namespaceShared3.Name,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
pak4n1, err := h.CreatePreAuthKey(n1.Name, false, false, nil)
|
preAuthKey2InShared1, err := app.CreatePreAuthKey(
|
||||||
|
namespaceShared1.Name,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
_, err = h.GetMachine(n1.Name, "test_get_shared_nodes_1")
|
_, err = app.GetMachine(namespaceShared1.Name, "test_get_shared_nodes_1")
|
||||||
c.Assert(err, check.NotNil)
|
c.Assert(err, check.NotNil)
|
||||||
|
|
||||||
m1 := &Machine{
|
machineInShared1 := &Machine{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
MachineKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
MachineKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
||||||
NodeKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
NodeKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
||||||
DiscoKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
DiscoKey: "686824e749f3b7f2a5927ee6c1e422aee5292592d9179a271ed7b3e659b44a66",
|
||||||
Name: "test_get_shared_nodes_1",
|
Name: "test_get_shared_nodes_1",
|
||||||
NamespaceID: n1.ID,
|
NamespaceID: namespaceShared1.ID,
|
||||||
Namespace: *n1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: "authKey",
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.1",
|
IPAddress: "100.64.0.1",
|
||||||
AuthKeyID: uint(pak1n1.ID),
|
AuthKeyID: uint(preAuthKeyInShared1.ID),
|
||||||
}
|
}
|
||||||
h.db.Save(m1)
|
app.db.Save(machineInShared1)
|
||||||
|
|
||||||
_, err = h.GetMachine(n1.Name, m1.Name)
|
_, err = app.GetMachine(namespaceShared1.Name, machineInShared1.Name)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
m2 := &Machine{
|
machineInShared2 := &Machine{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
Name: "test_get_shared_nodes_2",
|
Name: "test_get_shared_nodes_2",
|
||||||
NamespaceID: n2.ID,
|
NamespaceID: namespaceShared2.ID,
|
||||||
Namespace: *n2,
|
Namespace: *namespaceShared2,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: "authKey",
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.2",
|
IPAddress: "100.64.0.2",
|
||||||
AuthKeyID: uint(pak2n2.ID),
|
AuthKeyID: uint(preAuthKeyInShared2.ID),
|
||||||
}
|
}
|
||||||
h.db.Save(m2)
|
app.db.Save(machineInShared2)
|
||||||
|
|
||||||
_, err = h.GetMachine(n2.Name, m2.Name)
|
_, err = app.GetMachine(namespaceShared2.Name, machineInShared2.Name)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
m3 := &Machine{
|
machineInShared3 := &Machine{
|
||||||
ID: 3,
|
ID: 3,
|
||||||
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
Name: "test_get_shared_nodes_3",
|
Name: "test_get_shared_nodes_3",
|
||||||
NamespaceID: n3.ID,
|
NamespaceID: namespaceShared3.ID,
|
||||||
Namespace: *n3,
|
Namespace: *namespaceShared3,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: "authKey",
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.3",
|
IPAddress: "100.64.0.3",
|
||||||
AuthKeyID: uint(pak3n3.ID),
|
AuthKeyID: uint(preAuthKeyInShared3.ID),
|
||||||
}
|
}
|
||||||
h.db.Save(m3)
|
app.db.Save(machineInShared3)
|
||||||
|
|
||||||
_, err = h.GetMachine(n3.Name, m3.Name)
|
_, err = app.GetMachine(namespaceShared3.Name, machineInShared3.Name)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
m4 := &Machine{
|
machine2InShared1 := &Machine{
|
||||||
ID: 4,
|
ID: 4,
|
||||||
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
MachineKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
NodeKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
DiscoKey: "dec46ef9dc45c7d2f03bfcd5a640d9e24e3cc68ce3d9da223867c9bc6d5e9863",
|
||||||
Name: "test_get_shared_nodes_4",
|
Name: "test_get_shared_nodes_4",
|
||||||
NamespaceID: n1.ID,
|
NamespaceID: namespaceShared1.ID,
|
||||||
Namespace: *n1,
|
Namespace: *namespaceShared1,
|
||||||
Registered: true,
|
Registered: true,
|
||||||
RegisterMethod: "authKey",
|
RegisterMethod: RegisterMethodAuthKey,
|
||||||
IPAddress: "100.64.0.4",
|
IPAddress: "100.64.0.4",
|
||||||
AuthKeyID: uint(pak4n1.ID),
|
AuthKeyID: uint(preAuthKey2InShared1.ID),
|
||||||
}
|
}
|
||||||
h.db.Save(m4)
|
app.db.Save(machine2InShared1)
|
||||||
|
|
||||||
err = h.AddSharedMachineToNamespace(m2, n1)
|
err = app.AddSharedMachineToNamespace(machineInShared2, namespaceShared1)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
baseDomain := "foobar.headscale.net"
|
baseDomain := "foobar.headscale.net"
|
||||||
@ -295,11 +342,15 @@ func (s *Suite) TestDNSConfigMapResponseWithoutMagicDNS(c *check.C) {
|
|||||||
Proxied: false,
|
Proxied: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
m1peers, err := h.getPeers(m1)
|
peersOfMachine1Shared1, err := app.getPeers(machineInShared1)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
dnsConfig, err := getMapResponseDNSConfig(&dnsConfigOrig, baseDomain, *m1, m1peers)
|
dnsConfig := getMapResponseDNSConfig(
|
||||||
c.Assert(err, check.IsNil)
|
&dnsConfigOrig,
|
||||||
|
baseDomain,
|
||||||
|
*machineInShared1,
|
||||||
|
peersOfMachine1Shared1,
|
||||||
|
)
|
||||||
c.Assert(dnsConfig, check.NotNil)
|
c.Assert(dnsConfig, check.NotNil)
|
||||||
c.Assert(len(dnsConfig.Routes), check.Equals, 0)
|
c.Assert(len(dnsConfig.Routes), check.Equals, 0)
|
||||||
c.Assert(len(dnsConfig.Domains), check.Equals, 1)
|
c.Assert(len(dnsConfig.Domains), check.Equals, 1)
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
# Configuration reference
|
|
||||||
|
|
||||||
Headscale will look for a configuration file named `config.yaml` (or `config.json`) in the following order:
|
|
||||||
|
|
||||||
- `/etc/headscale`
|
|
||||||
- `~/.headscale`
|
|
||||||
- current working directory
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
server_url: http://headscale.mydomain.net
|
|
||||||
listen_addr: 0.0.0.0:8080
|
|
||||||
ip_prefix: 100.64.0.0/10
|
|
||||||
disable_check_updates: false
|
|
||||||
```
|
|
||||||
|
|
||||||
`server_url` is the external URL via which Headscale is reachable. `listen_addr` is the IP address and port the Headscale program should listen on. `ip_prefix` is the IP prefix (range) in which IP addresses for nodes will be allocated (default 100.64.0.0/10, e.g., 192.168.4.0/24, 10.0.0.0/8). `disable_check_updates` disables the automatic check for updates.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
log_level: debug
|
|
||||||
```
|
|
||||||
|
|
||||||
`log_level` can be used to set the Log level for Headscale, it defaults to `debug`, and the available levels are: `trace`, `debug`, `info`, `warn` and `error`.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
private_key_path: private.key
|
|
||||||
```
|
|
||||||
|
|
||||||
`private_key_path` is the path to the Wireguard private key. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
derp_map_path: derp.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
`derp_map_path` is the path to the [DERP](https://pkg.go.dev/tailscale.com/derp) map file. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
ephemeral_node_inactivity_timeout": "30m"
|
|
||||||
```
|
|
||||||
|
|
||||||
`ephemeral_node_inactivity_timeout` is the timeout after which inactive ephemeral node records will be deleted from the database. The default is 30 minutes. This value must be higher than 65 seconds (the keepalive timeout for the HTTP long poll is 60 seconds, plus a few seconds to avoid race conditions).
|
|
||||||
|
|
||||||
PostgresSQL
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
db_host: localhost
|
|
||||||
db_port: 5432
|
|
||||||
db_name: headscale
|
|
||||||
db_user: foo
|
|
||||||
db_pass: bar
|
|
||||||
```
|
|
||||||
|
|
||||||
SQLite
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
db_type: sqlite3
|
|
||||||
db_path: db.sqlite
|
|
||||||
```
|
|
||||||
|
|
||||||
The fields starting with `db_` are used for the DB connection information.
|
|
||||||
|
|
||||||
### TLS configuration
|
|
||||||
|
|
||||||
Please check [`TLS.md`](TLS.md).
|
|
||||||
|
|
||||||
### DNS configuration
|
|
||||||
|
|
||||||
Please refer to [`DNS.md`](DNS.md).
|
|
||||||
|
|
||||||
### 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 namespaces (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.
|
|
||||||
|
|
||||||
### Apple devices
|
|
||||||
|
|
||||||
An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance.
|
|
38
docs/DNS.md
38
docs/DNS.md
@ -1,38 +0,0 @@
|
|||||||
# DNS in headscale
|
|
||||||
|
|
||||||
headscale supports Tailscale's DNS configuration and MagicDNS. Please have a look to their KB to better understand what this means:
|
|
||||||
|
|
||||||
- https://tailscale.com/kb/1054/dns/
|
|
||||||
- https://tailscale.com/kb/1081/magicdns/
|
|
||||||
- https://tailscale.com/blog/2021-09-private-dns-with-magicdns/
|
|
||||||
|
|
||||||
Long story short, you can define the DNS servers you want to use in your tailnets, activate MagicDNS (so you don't have to remember the IP addresses of your nodes), define search domains, as well as predefined hosts. headscale will inject that settings into your nodes.
|
|
||||||
|
|
||||||
## Configuration reference
|
|
||||||
|
|
||||||
The setup is done via the `config.yaml` file, under the `dns_config` key.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
server_url: http://127.0.0.1:8001
|
|
||||||
listen_addr: 0.0.0.0:8001
|
|
||||||
private_key_path: private.key
|
|
||||||
dns_config:
|
|
||||||
nameservers:
|
|
||||||
- 1.1.1.1
|
|
||||||
- 8.8.8.8
|
|
||||||
restricted_nameservers:
|
|
||||||
foo.bar.com:
|
|
||||||
- 1.1.1.1
|
|
||||||
darp.headscale.net:
|
|
||||||
- 1.1.1.1
|
|
||||||
- 8.8.8.8
|
|
||||||
domains: []
|
|
||||||
magic_dns: true
|
|
||||||
base_domain: example.com
|
|
||||||
```
|
|
||||||
|
|
||||||
- `nameservers`: The list of DNS servers to use.
|
|
||||||
- `domains`: Search domains to inject.
|
|
||||||
- `magic_dns`: Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/). Only works if there is at least a nameserver defined.
|
|
||||||
- `base_domain`: Defines the base domain to create the hostnames for MagicDNS. `base_domain` must be a FQDNs, without the trailing dot. The FQDN of the hosts will be `hostname.namespace.base_domain` (e.g., _myhost.mynamespace.example.com_).
|
|
||||||
- `restricted_nameservers`: Split DNS (see https://tailscale.com/kb/1054/dns/), list of search domains and the DNS to query for each one.
|
|
42
docs/README.md
Normal file
42
docs/README.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# 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/XcQxk2VHjx) instead of opening an Issue.
|
||||||
|
|
||||||
|
## Official documentation
|
||||||
|
|
||||||
|
### How-to
|
||||||
|
|
||||||
|
- [Running headscale on Linux](running-headscale-linux.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)
|
||||||
|
|
||||||
|
## 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 namespaces (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.
|
||||||
|
|
||||||
|
### Apple devices
|
||||||
|
|
||||||
|
An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance.
|
149
docs/Running.md
149
docs/Running.md
@ -1,149 +0,0 @@
|
|||||||
# Running headscale
|
|
||||||
|
|
||||||
1. Download the headscale binary https://github.com/juanfont/headscale/releases, and place it somewhere in your $PATH or use the docker container
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker pull headscale/headscale:x.x.x
|
|
||||||
```
|
|
||||||
|
|
||||||
<!--
|
|
||||||
or
|
|
||||||
```shell
|
|
||||||
docker pull ghrc.io/juanfont/headscale:x.x.x
|
|
||||||
``` -->
|
|
||||||
|
|
||||||
2. (Optional, you can also use SQLite) Get yourself a PostgreSQL DB running
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run --name headscale \
|
|
||||||
-e POSTGRES_DB=headscale
|
|
||||||
-e POSTGRES_USER=foo \
|
|
||||||
-e POSTGRES_PASSWORD=bar \
|
|
||||||
-p 5432:5432 \
|
|
||||||
-d postgres
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Create a WireGuard private key and headscale configuration
|
|
||||||
|
|
||||||
```shell
|
|
||||||
wg genkey > private.key
|
|
||||||
|
|
||||||
cp config.yaml.example config.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Create a namespace
|
|
||||||
|
|
||||||
```shell
|
|
||||||
headscale namespaces create myfirstnamespace
|
|
||||||
```
|
|
||||||
|
|
||||||
or docker:
|
|
||||||
|
|
||||||
the db.sqlite mount is only needed if you use sqlite
|
|
||||||
|
|
||||||
```shell
|
|
||||||
touch db.sqlite
|
|
||||||
docker run \
|
|
||||||
-v $(pwd)/private.key:/private.key \
|
|
||||||
-v $(pwd)/config.json:/config.json \
|
|
||||||
-v $(pwd)/derp.yaml:/derp.yaml \
|
|
||||||
-v $(pwd)/db.sqlite:/db.sqlite \
|
|
||||||
-p 127.0.0.1:8080:8080 \
|
|
||||||
headscale/headscale:x.x.x \
|
|
||||||
headscale namespaces create myfirstnamespace
|
|
||||||
```
|
|
||||||
|
|
||||||
or if your server is already running in docker:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker exec <container_name> headscale create myfirstnamespace
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Run the server
|
|
||||||
|
|
||||||
```shell
|
|
||||||
headscale serve
|
|
||||||
```
|
|
||||||
|
|
||||||
or docker:
|
|
||||||
|
|
||||||
the db.sqlite mount is only needed if you use sqlite
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run \
|
|
||||||
-v $(pwd)/private.key:/private.key \
|
|
||||||
-v $(pwd)/config.json:/config.json \
|
|
||||||
-v $(pwd)/derp.yaml:/derp.yaml \
|
|
||||||
-v $(pwd)/db.sqlite:/db.sqlite \
|
|
||||||
-p 127.0.0.1:8080:8080 \
|
|
||||||
headscale/headscale:x.x.x headscale serve
|
|
||||||
```
|
|
||||||
|
|
||||||
6. If you used tailscale.com before in your nodes, make sure you clear the tailscaled data folder
|
|
||||||
|
|
||||||
```shell
|
|
||||||
systemctl stop tailscaled
|
|
||||||
rm -fr /var/lib/tailscale
|
|
||||||
systemctl start tailscaled
|
|
||||||
```
|
|
||||||
|
|
||||||
7. Add your first machine
|
|
||||||
|
|
||||||
```shell
|
|
||||||
tailscale up --login-server YOUR_HEADSCALE_URL
|
|
||||||
```
|
|
||||||
|
|
||||||
8. Navigate to the URL you will get with `tailscale up`, where you'll find your machine key.
|
|
||||||
|
|
||||||
9. In the server, register your machine to a namespace with the CLI
|
|
||||||
```shell
|
|
||||||
headscale -n myfirstnamespace nodes register YOURMACHINEKEY
|
|
||||||
```
|
|
||||||
or docker:
|
|
||||||
```shell
|
|
||||||
docker run \
|
|
||||||
-v $(pwd)/private.key:/private.key \
|
|
||||||
-v $(pwd)/config.json:/config.json \
|
|
||||||
-v $(pwd)/derp.yaml:/derp.yaml \
|
|
||||||
headscale/headscale:x.x.x \
|
|
||||||
headscale -n myfirstnamespace nodes register YOURMACHINEKEY
|
|
||||||
```
|
|
||||||
or if your server is already running in docker:
|
|
||||||
```shell
|
|
||||||
docker exec <container_name> headscale -n myfirstnamespace nodes register YOURMACHINEKEY
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can use Auth Keys to register your machines:
|
|
||||||
|
|
||||||
1. Create an authkey
|
|
||||||
|
|
||||||
```shell
|
|
||||||
headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h
|
|
||||||
```
|
|
||||||
|
|
||||||
or docker:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker run \
|
|
||||||
-v $(pwd)/private.key:/private.key \
|
|
||||||
-v $(pwd)/config.json:/config.json \
|
|
||||||
-v$(pwd)/derp.yaml:/derp.yaml \
|
|
||||||
-v $(pwd)/db.sqlite:/db.sqlite \
|
|
||||||
headscale/headscale:x.x.x \
|
|
||||||
headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h
|
|
||||||
```
|
|
||||||
|
|
||||||
or if your server is already running in docker:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
docker exec <container_name> headscale -n myfirstnamespace preauthkeys create --reusable --expiration 24h
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Use the authkey from your machine to register it
|
|
||||||
```shell
|
|
||||||
tailscale up --login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY
|
|
||||||
```
|
|
||||||
|
|
||||||
If you create an authkey with the `--ephemeral` flag, that key will create ephemeral nodes. This implies that `--reusable` is true.
|
|
||||||
|
|
||||||
Please bear in mind that all headscale commands support adding `-o json` or `-o json-line` to get nicely JSON-formatted output.
|
|
5
docs/examples/README.md
Normal file
5
docs/examples/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Examples
|
||||||
|
|
||||||
|
This directory contains examples on how to run `headscale` on different platforms.
|
||||||
|
|
||||||
|
All examples are provided by the community and they are not verified by the `headscale` authors.
|
@ -1,5 +1,7 @@
|
|||||||
# Deploying headscale on Kubernetes
|
# Deploying headscale on Kubernetes
|
||||||
|
|
||||||
|
**Note:** This is contributed by the community and not verified by the headscale authors.
|
||||||
|
|
||||||
This directory contains [Kustomize](https://kustomize.io) templates that deploy
|
This directory contains [Kustomize](https://kustomize.io) templates that deploy
|
||||||
headscale in various configurations.
|
headscale in various configurations.
|
||||||
|
|
||||||
@ -24,6 +26,7 @@ Configure DERP servers by editing `base/site/derp.yaml` if needed.
|
|||||||
You'll somehow need to get `headscale:latest` into your cluster image registry.
|
You'll somehow need to get `headscale:latest` into your cluster image registry.
|
||||||
|
|
||||||
An easy way to do this with k3s:
|
An easy way to do this with k3s:
|
||||||
|
|
||||||
- Reconfigure k3s to use docker instead of containerd (`k3s server --docker`)
|
- Reconfigure k3s to use docker instead of containerd (`k3s server --docker`)
|
||||||
- `docker build -t headscale:latest ..` from here
|
- `docker build -t headscale:latest ..` from here
|
||||||
|
|
||||||
@ -61,11 +64,11 @@ Use the wrapper script to remotely operate headscale to perform administrative
|
|||||||
tasks like creating namespaces, authkeys, etc.
|
tasks like creating namespaces, authkeys, etc.
|
||||||
|
|
||||||
```
|
```
|
||||||
[c@nix-slate:~/Projects/headscale/k8s]$ ./headscale.bash
|
[c@nix-slate:~/Projects/headscale/k8s]$ ./headscale.bash
|
||||||
|
|
||||||
headscale is an open source implementation of the Tailscale control server
|
headscale is an open source implementation of the Tailscale control server
|
||||||
|
|
||||||
https://gitlab.com/juanfont/headscale
|
https://github.com/juanfont/headscale
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
headscale [command]
|
headscale [command]
|
18
docs/examples/kustomize/base/ingress.yaml
Normal file
18
docs/examples/kustomize/base/ingress.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: traefik
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: $(PUBLIC_HOSTNAME)
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: headscale
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
42
docs/examples/kustomize/base/kustomization.yaml
Normal file
42
docs/examples/kustomize/base/kustomization.yaml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
namespace: headscale
|
||||||
|
resources:
|
||||||
|
- configmap.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- service.yaml
|
||||||
|
generatorOptions:
|
||||||
|
disableNameSuffixHash: true
|
||||||
|
configMapGenerator:
|
||||||
|
- name: headscale-site
|
||||||
|
files:
|
||||||
|
- derp.yaml=site/derp.yaml
|
||||||
|
envs:
|
||||||
|
- site/public.env
|
||||||
|
- name: headscale-etc
|
||||||
|
literals:
|
||||||
|
- config.json={}
|
||||||
|
secretGenerator:
|
||||||
|
- name: headscale
|
||||||
|
files:
|
||||||
|
- secrets/private-key
|
||||||
|
vars:
|
||||||
|
- name: PUBLIC_PROTO
|
||||||
|
objRef:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: headscale-site
|
||||||
|
apiVersion: v1
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: data.public-proto
|
||||||
|
- name: PUBLIC_HOSTNAME
|
||||||
|
objRef:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: headscale-site
|
||||||
|
apiVersion: v1
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: data.public-hostname
|
||||||
|
- name: CONTACT_EMAIL
|
||||||
|
objRef:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: headscale-site
|
||||||
|
apiVersion: v1
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: data.contact-email
|
@ -8,6 +8,6 @@ spec:
|
|||||||
selector:
|
selector:
|
||||||
app: headscale
|
app: headscale
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
targetPort: http
|
targetPort: http
|
||||||
port: 8080
|
port: 8080
|
76
docs/examples/kustomize/postgres/deployment.yaml
Normal file
76
docs/examples/kustomize/postgres/deployment.yaml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: headscale
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: headscale
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: headscale
|
||||||
|
image: "headscale:latest"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["/go/bin/headscale", "serve"]
|
||||||
|
env:
|
||||||
|
- name: SERVER_URL
|
||||||
|
value: $(PUBLIC_PROTO)://$(PUBLIC_HOSTNAME)
|
||||||
|
- name: LISTEN_ADDR
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: listen_addr
|
||||||
|
- name: DERP_MAP_PATH
|
||||||
|
value: /vol/config/derp.yaml
|
||||||
|
- name: EPHEMERAL_NODE_INACTIVITY_TIMEOUT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: ephemeral_node_inactivity_timeout
|
||||||
|
- name: DB_TYPE
|
||||||
|
value: postgres
|
||||||
|
- name: DB_HOST
|
||||||
|
value: postgres.headscale.svc.cluster.local
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "5432"
|
||||||
|
- name: DB_USER
|
||||||
|
value: headscale
|
||||||
|
- name: DB_PASS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgresql
|
||||||
|
key: password
|
||||||
|
- name: DB_NAME
|
||||||
|
value: headscale
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
protocol: TCP
|
||||||
|
containerPort: 8080
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /vol/config
|
||||||
|
- name: secret
|
||||||
|
mountPath: /vol/secret
|
||||||
|
- name: etc
|
||||||
|
mountPath: /etc/headscale
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: headscale-site
|
||||||
|
- name: etc
|
||||||
|
configMap:
|
||||||
|
name: headscale-etc
|
||||||
|
- name: secret
|
||||||
|
secret:
|
||||||
|
secretName: headscale
|
13
docs/examples/kustomize/postgres/kustomization.yaml
Normal file
13
docs/examples/kustomize/postgres/kustomization.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace: headscale
|
||||||
|
bases:
|
||||||
|
- ../base
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
- postgres-service.yaml
|
||||||
|
- postgres-statefulset.yaml
|
||||||
|
generatorOptions:
|
||||||
|
disableNameSuffixHash: true
|
||||||
|
secretGenerator:
|
||||||
|
- name: postgresql
|
||||||
|
files:
|
||||||
|
- secrets/password
|
@ -8,6 +8,6 @@ spec:
|
|||||||
selector:
|
selector:
|
||||||
app: postgres
|
app: postgres
|
||||||
ports:
|
ports:
|
||||||
- name: postgres
|
- name: postgres
|
||||||
targetPort: postgres
|
targetPort: postgres
|
||||||
port: 5432
|
port: 5432
|
49
docs/examples/kustomize/postgres/postgres-statefulset.yaml
Normal file
49
docs/examples/kustomize/postgres/postgres-statefulset.yaml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
spec:
|
||||||
|
serviceName: postgres
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: postgres
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: "postgres:13"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgresql
|
||||||
|
key: password
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: headscale
|
||||||
|
ports:
|
||||||
|
- name: postgres
|
||||||
|
protocol: TCP
|
||||||
|
containerPort: 5432
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 5432
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
volumeMounts:
|
||||||
|
- name: pgdata
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: pgdata
|
||||||
|
spec:
|
||||||
|
storageClassName: local-path
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
@ -6,6 +6,6 @@ metadata:
|
|||||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
spec:
|
spec:
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
- $(PUBLIC_HOSTNAME)
|
- $(PUBLIC_HOSTNAME)
|
||||||
secretName: production-cert
|
secretName: production-cert
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace: headscale
|
||||||
|
bases:
|
||||||
|
- ../base
|
||||||
|
resources:
|
||||||
|
- production-issuer.yaml
|
||||||
|
patches:
|
||||||
|
- path: ingress-patch.yaml
|
||||||
|
target:
|
||||||
|
kind: Ingress
|
@ -11,6 +11,6 @@ spec:
|
|||||||
# Secret resource used to store the account's private key.
|
# Secret resource used to store the account's private key.
|
||||||
name: letsencrypt-production-acc-key
|
name: letsencrypt-production-acc-key
|
||||||
solvers:
|
solvers:
|
||||||
- http01:
|
- http01:
|
||||||
ingress:
|
ingress:
|
||||||
class: traefik
|
class: traefik
|
@ -1,5 +1,5 @@
|
|||||||
namespace: headscale
|
namespace: headscale
|
||||||
bases:
|
bases:
|
||||||
- ../base
|
- ../base
|
||||||
resources:
|
resources:
|
||||||
- statefulset.yaml
|
- statefulset.yaml
|
77
docs/examples/kustomize/sqlite/statefulset.yaml
Normal file
77
docs/examples/kustomize/sqlite/statefulset.yaml
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
spec:
|
||||||
|
serviceName: headscale
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: headscale
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: headscale
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: headscale
|
||||||
|
image: "headscale:latest"
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["/go/bin/headscale", "serve"]
|
||||||
|
env:
|
||||||
|
- name: SERVER_URL
|
||||||
|
value: $(PUBLIC_PROTO)://$(PUBLIC_HOSTNAME)
|
||||||
|
- name: LISTEN_ADDR
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: listen_addr
|
||||||
|
- name: DERP_MAP_PATH
|
||||||
|
value: /vol/config/derp.yaml
|
||||||
|
- name: EPHEMERAL_NODE_INACTIVITY_TIMEOUT
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: headscale-config
|
||||||
|
key: ephemeral_node_inactivity_timeout
|
||||||
|
- name: DB_TYPE
|
||||||
|
value: sqlite3
|
||||||
|
- name: DB_PATH
|
||||||
|
value: /vol/data/db.sqlite
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
protocol: TCP
|
||||||
|
containerPort: 8080
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
periodSeconds: 15
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /vol/config
|
||||||
|
- name: data
|
||||||
|
mountPath: /vol/data
|
||||||
|
- name: secret
|
||||||
|
mountPath: /vol/secret
|
||||||
|
- name: etc
|
||||||
|
mountPath: /etc/headscale
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: headscale-site
|
||||||
|
- name: etc
|
||||||
|
configMap:
|
||||||
|
name: headscale-etc
|
||||||
|
- name: secret
|
||||||
|
secret:
|
||||||
|
secretName: headscale
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: data
|
||||||
|
spec:
|
||||||
|
storageClassName: local-path
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
@ -6,6 +6,6 @@ metadata:
|
|||||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||||
spec:
|
spec:
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
- $(PUBLIC_HOSTNAME)
|
- $(PUBLIC_HOSTNAME)
|
||||||
secretName: staging-cert
|
secretName: staging-cert
|
9
docs/examples/kustomize/staging-tls/kustomization.yaml
Normal file
9
docs/examples/kustomize/staging-tls/kustomization.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace: headscale
|
||||||
|
bases:
|
||||||
|
- ../base
|
||||||
|
resources:
|
||||||
|
- staging-issuer.yaml
|
||||||
|
patches:
|
||||||
|
- path: ingress-patch.yaml
|
||||||
|
target:
|
||||||
|
kind: Ingress
|
@ -11,6 +11,6 @@ spec:
|
|||||||
# Secret resource used to store the account's private key.
|
# Secret resource used to store the account's private key.
|
||||||
name: letsencrypt-staging-acc-key
|
name: letsencrypt-staging-acc-key
|
||||||
solvers:
|
solvers:
|
||||||
- http01:
|
- http01:
|
||||||
ingress:
|
ingress:
|
||||||
class: traefik
|
class: traefik
|
148
docs/running-headscale-container.md
Normal file
148
docs/running-headscale-container.md
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# Running headscale in a container
|
||||||
|
|
||||||
|
**Note:** the container documentation is maintained by the _community_ and there is no guarentee
|
||||||
|
it is up to date, or working.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
This documentation has the goal of showing a user how-to set up and run `headscale` in a container.
|
||||||
|
[Docker](https://www.docker.com) is used as the reference container implementation, but there is no reason that it should
|
||||||
|
not work with alternatives like [Podman](https://podman.io). The Docker image can be found on Docker Hub [here](https://hub.docker.com/r/headscale/headscale).
|
||||||
|
|
||||||
|
## Configure and run `headscale`
|
||||||
|
|
||||||
|
1. Prepare a directory on the host Docker node in your directory of choice, used to hold `headscale` configuration and the [SQLite](https://www.sqlite.org/) database:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mkdir ./headscale && cd ./headscale
|
||||||
|
mkdir ./config
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create an empty SQlite datebase in the headscale directory:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
touch ./config/db.sqlite
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **(Strongly Recommended)** Download a copy of the [example configuration](../config-example.yaml) from the [headscale repository](https://github.com/juanfont/headscale/).
|
||||||
|
|
||||||
|
Using wget:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
wget -O ./config/config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Using curl:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml -o ./config/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
**(Advanced)** If you would like to hand craft a config file **instead** of downloading the example config file, create a blank `headscale` configuration in the headscale directory to edit:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
touch ./config/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Modify the config file to your preferences before launching Docker container.
|
||||||
|
|
||||||
|
4. Start the headscale server while working in the host headscale directory:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run \
|
||||||
|
--name headscale \
|
||||||
|
--detach \
|
||||||
|
--rm \
|
||||||
|
--volume $(pwd)/config:/etc/headscale/ \
|
||||||
|
--publish 127.0.0.1:8080:8080 \
|
||||||
|
headscale/headscale:<VERSION> \
|
||||||
|
headscale serve
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will mount `config/` under `/etc/headscale`, forward port 8080 out of the container so the
|
||||||
|
`headscale` instance becomes available and then detach so headscale runs in the background.
|
||||||
|
|
||||||
|
5. Verify `headscale` is running:
|
||||||
|
|
||||||
|
Follow the container logs:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker logs --follow headscale
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify running containers:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker ps
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify `headscale` is available:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl http://127.0.0.1:8080/metrics
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Create a namespace ([tailnet](https://tailscale.com/kb/1136/tailnet/)):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker exec headscale -- headscale namespaces create myfirstnamespace
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register a machine (normal login)
|
||||||
|
|
||||||
|
On a client machine, execute the `tailscale` login command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
tailscale up --login-server YOUR_HEADSCALE_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
To register a machine when running `headscale` in a container, take the headscale command and pass it to the container:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker exec headscale -- \
|
||||||
|
headscale --namespace myfirstnamespace nodes register --key <YOU_+MACHINE_KEY>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register machine using a pre authenticated key
|
||||||
|
|
||||||
|
Generate a key using the command line:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker exec headscale -- \
|
||||||
|
headscale --namespace myfirstnamespace 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>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging headscale running in Docker
|
||||||
|
|
||||||
|
The `headscale/headscale` Docker container is based on a "distroless" image that does not contain a shell or any other debug tools. If you need to debug your application running in the Docker container, you can use the `-debug` variant, for example `headscale/headscale:x.x.x-debug`.
|
||||||
|
|
||||||
|
### Running the debug Docker container
|
||||||
|
|
||||||
|
To run the debug Docker container, use the exact same commands as above, but replace `headscale/headscale:x.x.x` with `headscale/headscale:x.x.x-debug` (`x.x.x` is the version of headscale). The two containers are compatible with each other, so you can alternate between them.
|
||||||
|
|
||||||
|
### Executing commands in the debug container
|
||||||
|
|
||||||
|
The default command in the debug container is to run `headscale`, which is located at `/bin/headscale` inside the container.
|
||||||
|
|
||||||
|
Additionally, the debug container includes a minimalist Busybox shell.
|
||||||
|
|
||||||
|
To launch a shell in the container, use:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -it headscale/headscale:x.x.x-debug sh
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also execute commands directly, such as `ls /bin` in this example:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run headscale/headscale:x.x.x-debug ls /bin
|
||||||
|
```
|
||||||
|
|
||||||
|
Using `docker exec` allows you to run commands in an existing container.
|
172
docs/running-headscale-linux.md
Normal file
172
docs/running-headscale-linux.md
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
# Running headscale on Linux
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Create an empty SQLite database:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
touch /var/lib/headscale/db.sqlite
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Create a `headscale` configuration:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
touch /etc/headscale/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
It is **strongly recommended** to copy and modifiy the [example configuration](../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:8080/metrics
|
||||||
|
```
|
||||||
|
|
||||||
|
8. Create a namespace ([tailnet](https://tailscale.com/kb/1136/tailnet/)):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
headscale namespaces create myfirstnamespace
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 --namespace myfirstnamespace nodes register --key <YOU_+MACHINE_KEY>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register machine using a pre authenticated key
|
||||||
|
|
||||||
|
Generate a key using the command line:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
headscale --namespace myfirstnamespace 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
|
||||||
|
|
||||||
|
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
|
||||||
|
ReadWritePaths=/var/lib/headscale /var/run/headscale
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
RuntimeDirectory=headscale
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
2. In `/etc/headscale/config.yaml`, override the default `headscale` unix socket with a SystemD friendly path:
|
||||||
|
|
||||||
|
```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 headscale
|
||||||
|
systemctl start headscale
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Verify the headscale service:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
systemctl status headscale
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify `headscale` is available:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl http://127.0.0.1:8080/metrics
|
||||||
|
```
|
||||||
|
|
||||||
|
`headscale` will now run in the background and start at boot.
|
@ -1,5 +1,9 @@
|
|||||||
# Running the service via TLS (optional)
|
# Running the service via TLS (optional)
|
||||||
|
|
||||||
|
## Let's Encrypt / ACME
|
||||||
|
|
||||||
|
To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/), set `tls_letsencrypt_hostname` to the desired certificate hostname. This name must resolve to the IP address(es) headscale is reachable on (i.e., it must correspond to the `server_url` configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured in `tls_letsencrypt_cache_dir`. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. The certificate will automatically be renewed as needed.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
tls_letsencrypt_hostname: ""
|
tls_letsencrypt_hostname: ""
|
||||||
tls_letsencrypt_listen: ":http"
|
tls_letsencrypt_listen: ":http"
|
||||||
@ -7,21 +11,21 @@ tls_letsencrypt_cache_dir: ".cache"
|
|||||||
tls_letsencrypt_challenge_type: HTTP-01
|
tls_letsencrypt_challenge_type: HTTP-01
|
||||||
```
|
```
|
||||||
|
|
||||||
To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/), set `tls_letsencrypt_hostname` to the desired certificate hostname. This name must resolve to the IP address(es) headscale is reachable on (i.e., it must correspond to the `server_url` configuration parameter). The certificate and Let's Encrypt account credentials will be stored in the directory configured in `tls_letsencrypt_cache_dir`. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from. The certificate will automatically be renewed as needed.
|
### Challenge type HTTP-01
|
||||||
|
|
||||||
```yaml
|
|
||||||
tls_cert_path: ""
|
|
||||||
tls_key_path: ""
|
|
||||||
```
|
|
||||||
|
|
||||||
headscale can also be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.
|
|
||||||
|
|
||||||
## Challenge type HTTP-01
|
|
||||||
|
|
||||||
The default challenge type `HTTP-01` requires that headscale is reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, headscale listens on port 80 on all local IPs for Let's Encrypt automated validation.
|
The default challenge type `HTTP-01` requires that headscale is reachable on port 80 for the Let's Encrypt automated validation, in addition to whatever port is configured in `listen_addr`. By default, headscale listens on port 80 on all local IPs for Let's Encrypt automated validation.
|
||||||
|
|
||||||
If you need to change the ip and/or port used by headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`.
|
If you need to change the ip and/or port used by headscale for the Let's Encrypt validation process, set `tls_letsencrypt_listen` to the appropriate value. This can be handy if you are running headscale as a non-root user (or can't run `setcap`). Keep in mind, however, that Let's Encrypt will _only_ connect to port 80 for the validation callback, so if you change `tls_letsencrypt_listen` you will also need to configure something else (e.g. a firewall rule) to forward the traffic from port 80 to the ip:port combination specified in `tls_letsencrypt_listen`.
|
||||||
|
|
||||||
## Challenge type TLS-ALPN-01
|
### Challenge type TLS-ALPN-01
|
||||||
|
|
||||||
Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`.
|
Alternatively, `tls_letsencrypt_challenge_type` can be set to `TLS-ALPN-01`. In this configuration, headscale listens on the ip:port combination defined in `listen_addr`. Let's Encrypt will _only_ connect to port 443 for the validation callback, so if `listen_addr` is not set to port 443, something else (e.g. a firewall rule) will be required to forward the traffic from port 443 to the ip:port combination specified in `listen_addr`.
|
||||||
|
|
||||||
|
## Bring your own certificate
|
||||||
|
|
||||||
|
headscale can also be configured to expose its web service via TLS. To configure the certificate and key file manually, set the `tls_cert_path` and `tls_cert_path` configuration parameters. If the path is relative, it will be interpreted as relative to the directory the configuration file was read from.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tls_cert_path: ""
|
||||||
|
tls_key_path: ""
|
||||||
|
```
|
1115
gen/go/headscale/v1/device.pb.go
Normal file
1115
gen/go/headscale/v1/device.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
303
gen/go/headscale/v1/headscale.pb.go
Normal file
303
gen/go/headscale/v1/headscale.pb.go
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.27.1
|
||||||
|
// protoc v3.18.1
|
||||||
|
// source: headscale/v1/headscale.proto
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "google.golang.org/genproto/googleapis/api/annotations"
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
var File_headscale_v1_headscale_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_headscale_v1_headscale_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x1c, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x68,
|
||||||
|
0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c,
|
||||||
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x1c, 0x67, 0x6f,
|
||||||
|
0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74,
|
||||||
|
0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x68, 0x65, 0x61, 0x64,
|
||||||
|
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
|
||||||
|
0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
||||||
|
0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65,
|
||||||
|
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
||||||
|
0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2e, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76,
|
||||||
|
0x31, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xf4,
|
||||||
|
0x12, 0x0a, 0x10, 0x48, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76,
|
||||||
|
0x69, 0x63, 0x65, 0x12, 0x77, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70,
|
||||||
|
0x61, 0x63, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
||||||
|
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52,
|
||||||
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
||||||
|
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
|
||||||
|
0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93,
|
||||||
|
0x02, 0x1a, 0x12, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65,
|
||||||
|
0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x7c, 0x0a, 0x0f,
|
||||||
|
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12,
|
||||||
|
0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43,
|
||||||
|
0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
|
||||||
|
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73,
|
||||||
|
0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3,
|
||||||
|
0xe4, 0x93, 0x02, 0x16, 0x22, 0x11, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61,
|
||||||
|
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x96, 0x01, 0x0a, 0x0f, 0x52,
|
||||||
|
0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x24,
|
||||||
|
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65,
|
||||||
|
0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
||||||
|
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70,
|
||||||
|
0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82, 0xd3, 0xe4,
|
||||||
|
0x93, 0x02, 0x30, 0x22, 0x2e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d,
|
||||||
|
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6f, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||||
|
0x7d, 0x2f, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x2f, 0x7b, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61,
|
||||||
|
0x6d, 0x65, 0x7d, 0x12, 0x80, 0x01, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61,
|
||||||
|
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
||||||
|
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d,
|
||||||
|
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e,
|
||||||
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c,
|
||||||
|
0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||||
|
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x2a, 0x18, 0x2f, 0x61,
|
||||||
|
0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f,
|
||||||
|
0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0x76, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61,
|
||||||
|
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
||||||
|
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65,
|
||||||
|
0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e,
|
||||||
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
|
||||||
|
0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
|
0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x61, 0x70,
|
||||||
|
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x80,
|
||||||
|
0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68,
|
||||||
|
0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
||||||
|
0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68,
|
||||||
|
0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x68, 0x65, 0x61,
|
||||||
|
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||||
|
0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||||
|
0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x61, 0x70, 0x69,
|
||||||
|
0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x3a, 0x01,
|
||||||
|
0x2a, 0x12, 0x87, 0x01, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41,
|
||||||
|
0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
||||||
|
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41,
|
||||||
|
0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e,
|
||||||
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70,
|
||||||
|
0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x22, 0x19, 0x2f,
|
||||||
|
0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65,
|
||||||
|
0x79, 0x2f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x7a, 0x0a, 0x0f, 0x4c,
|
||||||
|
0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x24,
|
||||||
|
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69,
|
||||||
|
0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
||||||
|
0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b,
|
||||||
|
0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, 0xe4,
|
||||||
|
0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x65,
|
||||||
|
0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x12, 0x89, 0x01, 0x0a, 0x12, 0x44, 0x65, 0x62, 0x75,
|
||||||
|
0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x27,
|
||||||
|
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65,
|
||||||
|
0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
||||||
|
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x43, 0x72, 0x65, 0x61,
|
||||||
|
0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
|
0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f,
|
||||||
|
0x76, 0x31, 0x2f, 0x64, 0x65, 0x62, 0x75, 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x3a, 0x01, 0x2a, 0x12, 0x75, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
|
0x65, 0x12, 0x1f, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
|
||||||
|
0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
|
0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
|
||||||
|
0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||||
|
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x12, 0x1c, 0x2f, 0x61,
|
||||||
|
0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d,
|
||||||
|
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x80, 0x01, 0x0a, 0x0f, 0x52,
|
||||||
|
0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x24,
|
||||||
|
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65,
|
||||||
|
0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
||||||
|
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68,
|
||||||
|
0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4,
|
||||||
|
0x93, 0x02, 0x1a, 0x22, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63,
|
||||||
|
0x68, 0x69, 0x6e, 0x65, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x7e, 0x0a,
|
||||||
|
0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x22,
|
||||||
|
0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65,
|
||||||
|
0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
|
0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76,
|
||||||
|
0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52,
|
||||||
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x2a,
|
||||||
|
0x1c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x85, 0x01,
|
||||||
|
0x0a, 0x0d, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12,
|
||||||
|
0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45,
|
||||||
|
0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75,
|
||||||
|
0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
||||||
|
0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25,
|
||||||
|
0x22, 0x23, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
|
0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x65,
|
||||||
|
0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x6e, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63,
|
||||||
|
0x68, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
|
||||||
|
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
||||||
|
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x68,
|
||||||
|
0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3,
|
||||||
|
0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61,
|
||||||
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x8d, 0x01, 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d,
|
||||||
|
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61,
|
||||||
|
0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69,
|
||||||
|
0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x65, 0x61, 0x64,
|
||||||
|
0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x4d, 0x61,
|
||||||
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x82,
|
||||||
|
0xd3, 0xe4, 0x93, 0x02, 0x30, 0x22, 0x2e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d,
|
||||||
|
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f,
|
||||||
|
0x69, 0x64, 0x7d, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73,
|
||||||
|
0x70, 0x61, 0x63, 0x65, 0x7d, 0x12, 0x95, 0x01, 0x0a, 0x0e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72,
|
||||||
|
0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x23, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73,
|
||||||
|
0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x4d,
|
||||||
|
0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e,
|
||||||
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x73,
|
||||||
|
0x68, 0x61, 0x72, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
|
0x6e, 0x73, 0x65, 0x22, 0x38, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x32, 0x22, 0x30, 0x2f, 0x61, 0x70,
|
||||||
|
0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61,
|
||||||
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x75, 0x6e, 0x73, 0x68, 0x61, 0x72,
|
||||||
|
0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x7d, 0x12, 0x8b, 0x01,
|
||||||
|
0x0a, 0x0f, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74,
|
||||||
|
0x65, 0x12, 0x24, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
|
||||||
|
0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
||||||
|
0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
|
0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b,
|
||||||
|
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x23, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f,
|
||||||
|
0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x97, 0x01, 0x0a, 0x13,
|
||||||
|
0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75,
|
||||||
|
0x74, 0x65, 0x73, 0x12, 0x28, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e,
|
||||||
|
0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65,
|
||||||
|
0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e,
|
||||||
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x6e, 0x61,
|
||||||
|
0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73,
|
||||||
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25,
|
||||||
|
0x22, 0x23, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e,
|
||||||
|
0x65, 0x2f, 0x7b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x72,
|
||||||
|
0x6f, 0x75, 0x74, 0x65, 0x73, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
|
||||||
|
0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61,
|
||||||
|
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31,
|
||||||
|
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_headscale_v1_headscale_proto_goTypes = []interface{}{
|
||||||
|
(*GetNamespaceRequest)(nil), // 0: headscale.v1.GetNamespaceRequest
|
||||||
|
(*CreateNamespaceRequest)(nil), // 1: headscale.v1.CreateNamespaceRequest
|
||||||
|
(*RenameNamespaceRequest)(nil), // 2: headscale.v1.RenameNamespaceRequest
|
||||||
|
(*DeleteNamespaceRequest)(nil), // 3: headscale.v1.DeleteNamespaceRequest
|
||||||
|
(*ListNamespacesRequest)(nil), // 4: headscale.v1.ListNamespacesRequest
|
||||||
|
(*CreatePreAuthKeyRequest)(nil), // 5: headscale.v1.CreatePreAuthKeyRequest
|
||||||
|
(*ExpirePreAuthKeyRequest)(nil), // 6: headscale.v1.ExpirePreAuthKeyRequest
|
||||||
|
(*ListPreAuthKeysRequest)(nil), // 7: headscale.v1.ListPreAuthKeysRequest
|
||||||
|
(*DebugCreateMachineRequest)(nil), // 8: headscale.v1.DebugCreateMachineRequest
|
||||||
|
(*GetMachineRequest)(nil), // 9: headscale.v1.GetMachineRequest
|
||||||
|
(*RegisterMachineRequest)(nil), // 10: headscale.v1.RegisterMachineRequest
|
||||||
|
(*DeleteMachineRequest)(nil), // 11: headscale.v1.DeleteMachineRequest
|
||||||
|
(*ExpireMachineRequest)(nil), // 12: headscale.v1.ExpireMachineRequest
|
||||||
|
(*ListMachinesRequest)(nil), // 13: headscale.v1.ListMachinesRequest
|
||||||
|
(*ShareMachineRequest)(nil), // 14: headscale.v1.ShareMachineRequest
|
||||||
|
(*UnshareMachineRequest)(nil), // 15: headscale.v1.UnshareMachineRequest
|
||||||
|
(*GetMachineRouteRequest)(nil), // 16: headscale.v1.GetMachineRouteRequest
|
||||||
|
(*EnableMachineRoutesRequest)(nil), // 17: headscale.v1.EnableMachineRoutesRequest
|
||||||
|
(*GetNamespaceResponse)(nil), // 18: headscale.v1.GetNamespaceResponse
|
||||||
|
(*CreateNamespaceResponse)(nil), // 19: headscale.v1.CreateNamespaceResponse
|
||||||
|
(*RenameNamespaceResponse)(nil), // 20: headscale.v1.RenameNamespaceResponse
|
||||||
|
(*DeleteNamespaceResponse)(nil), // 21: headscale.v1.DeleteNamespaceResponse
|
||||||
|
(*ListNamespacesResponse)(nil), // 22: headscale.v1.ListNamespacesResponse
|
||||||
|
(*CreatePreAuthKeyResponse)(nil), // 23: headscale.v1.CreatePreAuthKeyResponse
|
||||||
|
(*ExpirePreAuthKeyResponse)(nil), // 24: headscale.v1.ExpirePreAuthKeyResponse
|
||||||
|
(*ListPreAuthKeysResponse)(nil), // 25: headscale.v1.ListPreAuthKeysResponse
|
||||||
|
(*DebugCreateMachineResponse)(nil), // 26: headscale.v1.DebugCreateMachineResponse
|
||||||
|
(*GetMachineResponse)(nil), // 27: headscale.v1.GetMachineResponse
|
||||||
|
(*RegisterMachineResponse)(nil), // 28: headscale.v1.RegisterMachineResponse
|
||||||
|
(*DeleteMachineResponse)(nil), // 29: headscale.v1.DeleteMachineResponse
|
||||||
|
(*ExpireMachineResponse)(nil), // 30: headscale.v1.ExpireMachineResponse
|
||||||
|
(*ListMachinesResponse)(nil), // 31: headscale.v1.ListMachinesResponse
|
||||||
|
(*ShareMachineResponse)(nil), // 32: headscale.v1.ShareMachineResponse
|
||||||
|
(*UnshareMachineResponse)(nil), // 33: headscale.v1.UnshareMachineResponse
|
||||||
|
(*GetMachineRouteResponse)(nil), // 34: headscale.v1.GetMachineRouteResponse
|
||||||
|
(*EnableMachineRoutesResponse)(nil), // 35: headscale.v1.EnableMachineRoutesResponse
|
||||||
|
}
|
||||||
|
var file_headscale_v1_headscale_proto_depIdxs = []int32{
|
||||||
|
0, // 0: headscale.v1.HeadscaleService.GetNamespace:input_type -> headscale.v1.GetNamespaceRequest
|
||||||
|
1, // 1: headscale.v1.HeadscaleService.CreateNamespace:input_type -> headscale.v1.CreateNamespaceRequest
|
||||||
|
2, // 2: headscale.v1.HeadscaleService.RenameNamespace:input_type -> headscale.v1.RenameNamespaceRequest
|
||||||
|
3, // 3: headscale.v1.HeadscaleService.DeleteNamespace:input_type -> headscale.v1.DeleteNamespaceRequest
|
||||||
|
4, // 4: headscale.v1.HeadscaleService.ListNamespaces:input_type -> headscale.v1.ListNamespacesRequest
|
||||||
|
5, // 5: headscale.v1.HeadscaleService.CreatePreAuthKey:input_type -> headscale.v1.CreatePreAuthKeyRequest
|
||||||
|
6, // 6: headscale.v1.HeadscaleService.ExpirePreAuthKey:input_type -> headscale.v1.ExpirePreAuthKeyRequest
|
||||||
|
7, // 7: headscale.v1.HeadscaleService.ListPreAuthKeys:input_type -> headscale.v1.ListPreAuthKeysRequest
|
||||||
|
8, // 8: headscale.v1.HeadscaleService.DebugCreateMachine:input_type -> headscale.v1.DebugCreateMachineRequest
|
||||||
|
9, // 9: headscale.v1.HeadscaleService.GetMachine:input_type -> headscale.v1.GetMachineRequest
|
||||||
|
10, // 10: headscale.v1.HeadscaleService.RegisterMachine:input_type -> headscale.v1.RegisterMachineRequest
|
||||||
|
11, // 11: headscale.v1.HeadscaleService.DeleteMachine:input_type -> headscale.v1.DeleteMachineRequest
|
||||||
|
12, // 12: headscale.v1.HeadscaleService.ExpireMachine:input_type -> headscale.v1.ExpireMachineRequest
|
||||||
|
13, // 13: headscale.v1.HeadscaleService.ListMachines:input_type -> headscale.v1.ListMachinesRequest
|
||||||
|
14, // 14: headscale.v1.HeadscaleService.ShareMachine:input_type -> headscale.v1.ShareMachineRequest
|
||||||
|
15, // 15: headscale.v1.HeadscaleService.UnshareMachine:input_type -> headscale.v1.UnshareMachineRequest
|
||||||
|
16, // 16: headscale.v1.HeadscaleService.GetMachineRoute:input_type -> headscale.v1.GetMachineRouteRequest
|
||||||
|
17, // 17: headscale.v1.HeadscaleService.EnableMachineRoutes:input_type -> headscale.v1.EnableMachineRoutesRequest
|
||||||
|
18, // 18: headscale.v1.HeadscaleService.GetNamespace:output_type -> headscale.v1.GetNamespaceResponse
|
||||||
|
19, // 19: headscale.v1.HeadscaleService.CreateNamespace:output_type -> headscale.v1.CreateNamespaceResponse
|
||||||
|
20, // 20: headscale.v1.HeadscaleService.RenameNamespace:output_type -> headscale.v1.RenameNamespaceResponse
|
||||||
|
21, // 21: headscale.v1.HeadscaleService.DeleteNamespace:output_type -> headscale.v1.DeleteNamespaceResponse
|
||||||
|
22, // 22: headscale.v1.HeadscaleService.ListNamespaces:output_type -> headscale.v1.ListNamespacesResponse
|
||||||
|
23, // 23: headscale.v1.HeadscaleService.CreatePreAuthKey:output_type -> headscale.v1.CreatePreAuthKeyResponse
|
||||||
|
24, // 24: headscale.v1.HeadscaleService.ExpirePreAuthKey:output_type -> headscale.v1.ExpirePreAuthKeyResponse
|
||||||
|
25, // 25: headscale.v1.HeadscaleService.ListPreAuthKeys:output_type -> headscale.v1.ListPreAuthKeysResponse
|
||||||
|
26, // 26: headscale.v1.HeadscaleService.DebugCreateMachine:output_type -> headscale.v1.DebugCreateMachineResponse
|
||||||
|
27, // 27: headscale.v1.HeadscaleService.GetMachine:output_type -> headscale.v1.GetMachineResponse
|
||||||
|
28, // 28: headscale.v1.HeadscaleService.RegisterMachine:output_type -> headscale.v1.RegisterMachineResponse
|
||||||
|
29, // 29: headscale.v1.HeadscaleService.DeleteMachine:output_type -> headscale.v1.DeleteMachineResponse
|
||||||
|
30, // 30: headscale.v1.HeadscaleService.ExpireMachine:output_type -> headscale.v1.ExpireMachineResponse
|
||||||
|
31, // 31: headscale.v1.HeadscaleService.ListMachines:output_type -> headscale.v1.ListMachinesResponse
|
||||||
|
32, // 32: headscale.v1.HeadscaleService.ShareMachine:output_type -> headscale.v1.ShareMachineResponse
|
||||||
|
33, // 33: headscale.v1.HeadscaleService.UnshareMachine:output_type -> headscale.v1.UnshareMachineResponse
|
||||||
|
34, // 34: headscale.v1.HeadscaleService.GetMachineRoute:output_type -> headscale.v1.GetMachineRouteResponse
|
||||||
|
35, // 35: headscale.v1.HeadscaleService.EnableMachineRoutes:output_type -> headscale.v1.EnableMachineRoutesResponse
|
||||||
|
18, // [18:36] is the sub-list for method output_type
|
||||||
|
0, // [0:18] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_headscale_v1_headscale_proto_init() }
|
||||||
|
func file_headscale_v1_headscale_proto_init() {
|
||||||
|
if File_headscale_v1_headscale_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_init()
|
||||||
|
file_headscale_v1_preauthkey_proto_init()
|
||||||
|
file_headscale_v1_machine_proto_init()
|
||||||
|
file_headscale_v1_routes_proto_init()
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_headscale_v1_headscale_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 0,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_headscale_v1_headscale_proto_goTypes,
|
||||||
|
DependencyIndexes: file_headscale_v1_headscale_proto_depIdxs,
|
||||||
|
}.Build()
|
||||||
|
File_headscale_v1_headscale_proto = out.File
|
||||||
|
file_headscale_v1_headscale_proto_rawDesc = nil
|
||||||
|
file_headscale_v1_headscale_proto_goTypes = nil
|
||||||
|
file_headscale_v1_headscale_proto_depIdxs = nil
|
||||||
|
}
|
1792
gen/go/headscale/v1/headscale.pb.gw.go
Normal file
1792
gen/go/headscale/v1/headscale.pb.gw.go
Normal file
File diff suppressed because it is too large
Load Diff
721
gen/go/headscale/v1/headscale_grpc.pb.go
Normal file
721
gen/go/headscale/v1/headscale_grpc.pb.go
Normal file
@ -0,0 +1,721 @@
|
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.32.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion7
|
||||||
|
|
||||||
|
// HeadscaleServiceClient is the client API for HeadscaleService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type HeadscaleServiceClient interface {
|
||||||
|
// --- Namespace start ---
|
||||||
|
GetNamespace(ctx context.Context, in *GetNamespaceRequest, opts ...grpc.CallOption) (*GetNamespaceResponse, error)
|
||||||
|
CreateNamespace(ctx context.Context, in *CreateNamespaceRequest, opts ...grpc.CallOption) (*CreateNamespaceResponse, error)
|
||||||
|
RenameNamespace(ctx context.Context, in *RenameNamespaceRequest, opts ...grpc.CallOption) (*RenameNamespaceResponse, error)
|
||||||
|
DeleteNamespace(ctx context.Context, in *DeleteNamespaceRequest, opts ...grpc.CallOption) (*DeleteNamespaceResponse, error)
|
||||||
|
ListNamespaces(ctx context.Context, in *ListNamespacesRequest, opts ...grpc.CallOption) (*ListNamespacesResponse, error)
|
||||||
|
// --- PreAuthKeys start ---
|
||||||
|
CreatePreAuthKey(ctx context.Context, in *CreatePreAuthKeyRequest, opts ...grpc.CallOption) (*CreatePreAuthKeyResponse, error)
|
||||||
|
ExpirePreAuthKey(ctx context.Context, in *ExpirePreAuthKeyRequest, opts ...grpc.CallOption) (*ExpirePreAuthKeyResponse, error)
|
||||||
|
ListPreAuthKeys(ctx context.Context, in *ListPreAuthKeysRequest, opts ...grpc.CallOption) (*ListPreAuthKeysResponse, error)
|
||||||
|
// --- Machine start ---
|
||||||
|
DebugCreateMachine(ctx context.Context, in *DebugCreateMachineRequest, opts ...grpc.CallOption) (*DebugCreateMachineResponse, error)
|
||||||
|
GetMachine(ctx context.Context, in *GetMachineRequest, opts ...grpc.CallOption) (*GetMachineResponse, error)
|
||||||
|
RegisterMachine(ctx context.Context, in *RegisterMachineRequest, opts ...grpc.CallOption) (*RegisterMachineResponse, error)
|
||||||
|
DeleteMachine(ctx context.Context, in *DeleteMachineRequest, opts ...grpc.CallOption) (*DeleteMachineResponse, error)
|
||||||
|
ExpireMachine(ctx context.Context, in *ExpireMachineRequest, opts ...grpc.CallOption) (*ExpireMachineResponse, error)
|
||||||
|
ListMachines(ctx context.Context, in *ListMachinesRequest, opts ...grpc.CallOption) (*ListMachinesResponse, error)
|
||||||
|
ShareMachine(ctx context.Context, in *ShareMachineRequest, opts ...grpc.CallOption) (*ShareMachineResponse, error)
|
||||||
|
UnshareMachine(ctx context.Context, in *UnshareMachineRequest, opts ...grpc.CallOption) (*UnshareMachineResponse, error)
|
||||||
|
// --- Route start ---
|
||||||
|
GetMachineRoute(ctx context.Context, in *GetMachineRouteRequest, opts ...grpc.CallOption) (*GetMachineRouteResponse, error)
|
||||||
|
EnableMachineRoutes(ctx context.Context, in *EnableMachineRoutesRequest, opts ...grpc.CallOption) (*EnableMachineRoutesResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type headscaleServiceClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHeadscaleServiceClient(cc grpc.ClientConnInterface) HeadscaleServiceClient {
|
||||||
|
return &headscaleServiceClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) GetNamespace(ctx context.Context, in *GetNamespaceRequest, opts ...grpc.CallOption) (*GetNamespaceResponse, error) {
|
||||||
|
out := new(GetNamespaceResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetNamespace", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) CreateNamespace(ctx context.Context, in *CreateNamespaceRequest, opts ...grpc.CallOption) (*CreateNamespaceResponse, error) {
|
||||||
|
out := new(CreateNamespaceResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/CreateNamespace", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) RenameNamespace(ctx context.Context, in *RenameNamespaceRequest, opts ...grpc.CallOption) (*RenameNamespaceResponse, error) {
|
||||||
|
out := new(RenameNamespaceResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/RenameNamespace", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) DeleteNamespace(ctx context.Context, in *DeleteNamespaceRequest, opts ...grpc.CallOption) (*DeleteNamespaceResponse, error) {
|
||||||
|
out := new(DeleteNamespaceResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DeleteNamespace", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) ListNamespaces(ctx context.Context, in *ListNamespacesRequest, opts ...grpc.CallOption) (*ListNamespacesResponse, error) {
|
||||||
|
out := new(ListNamespacesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListNamespaces", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) CreatePreAuthKey(ctx context.Context, in *CreatePreAuthKeyRequest, opts ...grpc.CallOption) (*CreatePreAuthKeyResponse, error) {
|
||||||
|
out := new(CreatePreAuthKeyResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/CreatePreAuthKey", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) ExpirePreAuthKey(ctx context.Context, in *ExpirePreAuthKeyRequest, opts ...grpc.CallOption) (*ExpirePreAuthKeyResponse, error) {
|
||||||
|
out := new(ExpirePreAuthKeyResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ExpirePreAuthKey", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) ListPreAuthKeys(ctx context.Context, in *ListPreAuthKeysRequest, opts ...grpc.CallOption) (*ListPreAuthKeysResponse, error) {
|
||||||
|
out := new(ListPreAuthKeysResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListPreAuthKeys", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) DebugCreateMachine(ctx context.Context, in *DebugCreateMachineRequest, opts ...grpc.CallOption) (*DebugCreateMachineResponse, error) {
|
||||||
|
out := new(DebugCreateMachineResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DebugCreateMachine", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) GetMachine(ctx context.Context, in *GetMachineRequest, opts ...grpc.CallOption) (*GetMachineResponse, error) {
|
||||||
|
out := new(GetMachineResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetMachine", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) RegisterMachine(ctx context.Context, in *RegisterMachineRequest, opts ...grpc.CallOption) (*RegisterMachineResponse, error) {
|
||||||
|
out := new(RegisterMachineResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/RegisterMachine", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) DeleteMachine(ctx context.Context, in *DeleteMachineRequest, opts ...grpc.CallOption) (*DeleteMachineResponse, error) {
|
||||||
|
out := new(DeleteMachineResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DeleteMachine", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) ExpireMachine(ctx context.Context, in *ExpireMachineRequest, opts ...grpc.CallOption) (*ExpireMachineResponse, error) {
|
||||||
|
out := new(ExpireMachineResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ExpireMachine", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) ListMachines(ctx context.Context, in *ListMachinesRequest, opts ...grpc.CallOption) (*ListMachinesResponse, error) {
|
||||||
|
out := new(ListMachinesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListMachines", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) ShareMachine(ctx context.Context, in *ShareMachineRequest, opts ...grpc.CallOption) (*ShareMachineResponse, error) {
|
||||||
|
out := new(ShareMachineResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ShareMachine", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) UnshareMachine(ctx context.Context, in *UnshareMachineRequest, opts ...grpc.CallOption) (*UnshareMachineResponse, error) {
|
||||||
|
out := new(UnshareMachineResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/UnshareMachine", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) GetMachineRoute(ctx context.Context, in *GetMachineRouteRequest, opts ...grpc.CallOption) (*GetMachineRouteResponse, error) {
|
||||||
|
out := new(GetMachineRouteResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetMachineRoute", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) EnableMachineRoutes(ctx context.Context, in *EnableMachineRoutesRequest, opts ...grpc.CallOption) (*EnableMachineRoutesResponse, error) {
|
||||||
|
out := new(EnableMachineRoutesResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/EnableMachineRoutes", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadscaleServiceServer is the server API for HeadscaleService service.
|
||||||
|
// All implementations must embed UnimplementedHeadscaleServiceServer
|
||||||
|
// for forward compatibility
|
||||||
|
type HeadscaleServiceServer interface {
|
||||||
|
// --- Namespace start ---
|
||||||
|
GetNamespace(context.Context, *GetNamespaceRequest) (*GetNamespaceResponse, error)
|
||||||
|
CreateNamespace(context.Context, *CreateNamespaceRequest) (*CreateNamespaceResponse, error)
|
||||||
|
RenameNamespace(context.Context, *RenameNamespaceRequest) (*RenameNamespaceResponse, error)
|
||||||
|
DeleteNamespace(context.Context, *DeleteNamespaceRequest) (*DeleteNamespaceResponse, error)
|
||||||
|
ListNamespaces(context.Context, *ListNamespacesRequest) (*ListNamespacesResponse, error)
|
||||||
|
// --- PreAuthKeys start ---
|
||||||
|
CreatePreAuthKey(context.Context, *CreatePreAuthKeyRequest) (*CreatePreAuthKeyResponse, error)
|
||||||
|
ExpirePreAuthKey(context.Context, *ExpirePreAuthKeyRequest) (*ExpirePreAuthKeyResponse, error)
|
||||||
|
ListPreAuthKeys(context.Context, *ListPreAuthKeysRequest) (*ListPreAuthKeysResponse, error)
|
||||||
|
// --- Machine start ---
|
||||||
|
DebugCreateMachine(context.Context, *DebugCreateMachineRequest) (*DebugCreateMachineResponse, error)
|
||||||
|
GetMachine(context.Context, *GetMachineRequest) (*GetMachineResponse, error)
|
||||||
|
RegisterMachine(context.Context, *RegisterMachineRequest) (*RegisterMachineResponse, error)
|
||||||
|
DeleteMachine(context.Context, *DeleteMachineRequest) (*DeleteMachineResponse, error)
|
||||||
|
ExpireMachine(context.Context, *ExpireMachineRequest) (*ExpireMachineResponse, error)
|
||||||
|
ListMachines(context.Context, *ListMachinesRequest) (*ListMachinesResponse, error)
|
||||||
|
ShareMachine(context.Context, *ShareMachineRequest) (*ShareMachineResponse, error)
|
||||||
|
UnshareMachine(context.Context, *UnshareMachineRequest) (*UnshareMachineResponse, error)
|
||||||
|
// --- Route start ---
|
||||||
|
GetMachineRoute(context.Context, *GetMachineRouteRequest) (*GetMachineRouteResponse, error)
|
||||||
|
EnableMachineRoutes(context.Context, *EnableMachineRoutesRequest) (*EnableMachineRoutesResponse, error)
|
||||||
|
mustEmbedUnimplementedHeadscaleServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedHeadscaleServiceServer must be embedded to have forward compatible implementations.
|
||||||
|
type UnimplementedHeadscaleServiceServer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedHeadscaleServiceServer) GetNamespace(context.Context, *GetNamespaceRequest) (*GetNamespaceResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetNamespace not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) CreateNamespace(context.Context, *CreateNamespaceRequest) (*CreateNamespaceResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method CreateNamespace not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) RenameNamespace(context.Context, *RenameNamespaceRequest) (*RenameNamespaceResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method RenameNamespace not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) DeleteNamespace(context.Context, *DeleteNamespaceRequest) (*DeleteNamespaceResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method DeleteNamespace not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) ListNamespaces(context.Context, *ListNamespacesRequest) (*ListNamespacesResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListNamespaces not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) CreatePreAuthKey(context.Context, *CreatePreAuthKeyRequest) (*CreatePreAuthKeyResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method CreatePreAuthKey not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) ExpirePreAuthKey(context.Context, *ExpirePreAuthKeyRequest) (*ExpirePreAuthKeyResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ExpirePreAuthKey not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) ListPreAuthKeys(context.Context, *ListPreAuthKeysRequest) (*ListPreAuthKeysResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListPreAuthKeys not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) DebugCreateMachine(context.Context, *DebugCreateMachineRequest) (*DebugCreateMachineResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method DebugCreateMachine not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) GetMachine(context.Context, *GetMachineRequest) (*GetMachineResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetMachine not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) RegisterMachine(context.Context, *RegisterMachineRequest) (*RegisterMachineResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method RegisterMachine not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) DeleteMachine(context.Context, *DeleteMachineRequest) (*DeleteMachineResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method DeleteMachine not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) ExpireMachine(context.Context, *ExpireMachineRequest) (*ExpireMachineResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ExpireMachine not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) ListMachines(context.Context, *ListMachinesRequest) (*ListMachinesResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListMachines not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) ShareMachine(context.Context, *ShareMachineRequest) (*ShareMachineResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ShareMachine not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) UnshareMachine(context.Context, *UnshareMachineRequest) (*UnshareMachineResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method UnshareMachine not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) GetMachineRoute(context.Context, *GetMachineRouteRequest) (*GetMachineRouteResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetMachineRoute not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) EnableMachineRoutes(context.Context, *EnableMachineRoutesRequest) (*EnableMachineRoutesResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method EnableMachineRoutes not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
|
||||||
|
|
||||||
|
// UnsafeHeadscaleServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to HeadscaleServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeHeadscaleServiceServer interface {
|
||||||
|
mustEmbedUnimplementedHeadscaleServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterHeadscaleServiceServer(s grpc.ServiceRegistrar, srv HeadscaleServiceServer) {
|
||||||
|
s.RegisterService(&HeadscaleService_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_GetNamespace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetNamespaceRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).GetNamespace(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/GetNamespace",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).GetNamespace(ctx, req.(*GetNamespaceRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_CreateNamespace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(CreateNamespaceRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).CreateNamespace(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/CreateNamespace",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).CreateNamespace(ctx, req.(*CreateNamespaceRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_RenameNamespace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(RenameNamespaceRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).RenameNamespace(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/RenameNamespace",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).RenameNamespace(ctx, req.(*RenameNamespaceRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_DeleteNamespace_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(DeleteNamespaceRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).DeleteNamespace(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/DeleteNamespace",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).DeleteNamespace(ctx, req.(*DeleteNamespaceRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_ListNamespaces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListNamespacesRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).ListNamespaces(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/ListNamespaces",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).ListNamespaces(ctx, req.(*ListNamespacesRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_CreatePreAuthKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(CreatePreAuthKeyRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).CreatePreAuthKey(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/CreatePreAuthKey",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).CreatePreAuthKey(ctx, req.(*CreatePreAuthKeyRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_ExpirePreAuthKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ExpirePreAuthKeyRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).ExpirePreAuthKey(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/ExpirePreAuthKey",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).ExpirePreAuthKey(ctx, req.(*ExpirePreAuthKeyRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_ListPreAuthKeys_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListPreAuthKeysRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).ListPreAuthKeys(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/ListPreAuthKeys",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).ListPreAuthKeys(ctx, req.(*ListPreAuthKeysRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_DebugCreateMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(DebugCreateMachineRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).DebugCreateMachine(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/DebugCreateMachine",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).DebugCreateMachine(ctx, req.(*DebugCreateMachineRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_GetMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetMachineRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).GetMachine(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/GetMachine",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).GetMachine(ctx, req.(*GetMachineRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_RegisterMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(RegisterMachineRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).RegisterMachine(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/RegisterMachine",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).RegisterMachine(ctx, req.(*RegisterMachineRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_DeleteMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(DeleteMachineRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).DeleteMachine(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/DeleteMachine",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).DeleteMachine(ctx, req.(*DeleteMachineRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_ExpireMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ExpireMachineRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).ExpireMachine(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/ExpireMachine",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).ExpireMachine(ctx, req.(*ExpireMachineRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_ListMachines_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListMachinesRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).ListMachines(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/ListMachines",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).ListMachines(ctx, req.(*ListMachinesRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_ShareMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ShareMachineRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).ShareMachine(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/ShareMachine",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).ShareMachine(ctx, req.(*ShareMachineRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_UnshareMachine_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(UnshareMachineRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).UnshareMachine(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/UnshareMachine",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).UnshareMachine(ctx, req.(*UnshareMachineRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_GetMachineRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetMachineRouteRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).GetMachineRoute(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/GetMachineRoute",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).GetMachineRoute(ctx, req.(*GetMachineRouteRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_EnableMachineRoutes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(EnableMachineRoutesRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).EnableMachineRoutes(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/headscale.v1.HeadscaleService/EnableMachineRoutes",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).EnableMachineRoutes(ctx, req.(*EnableMachineRoutesRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadscaleService_ServiceDesc is the grpc.ServiceDesc for HeadscaleService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var HeadscaleService_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "headscale.v1.HeadscaleService",
|
||||||
|
HandlerType: (*HeadscaleServiceServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "GetNamespace",
|
||||||
|
Handler: _HeadscaleService_GetNamespace_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "CreateNamespace",
|
||||||
|
Handler: _HeadscaleService_CreateNamespace_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "RenameNamespace",
|
||||||
|
Handler: _HeadscaleService_RenameNamespace_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "DeleteNamespace",
|
||||||
|
Handler: _HeadscaleService_DeleteNamespace_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListNamespaces",
|
||||||
|
Handler: _HeadscaleService_ListNamespaces_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "CreatePreAuthKey",
|
||||||
|
Handler: _HeadscaleService_CreatePreAuthKey_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ExpirePreAuthKey",
|
||||||
|
Handler: _HeadscaleService_ExpirePreAuthKey_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListPreAuthKeys",
|
||||||
|
Handler: _HeadscaleService_ListPreAuthKeys_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "DebugCreateMachine",
|
||||||
|
Handler: _HeadscaleService_DebugCreateMachine_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetMachine",
|
||||||
|
Handler: _HeadscaleService_GetMachine_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "RegisterMachine",
|
||||||
|
Handler: _HeadscaleService_RegisterMachine_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "DeleteMachine",
|
||||||
|
Handler: _HeadscaleService_DeleteMachine_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ExpireMachine",
|
||||||
|
Handler: _HeadscaleService_ExpireMachine_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListMachines",
|
||||||
|
Handler: _HeadscaleService_ListMachines_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ShareMachine",
|
||||||
|
Handler: _HeadscaleService_ShareMachine_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "UnshareMachine",
|
||||||
|
Handler: _HeadscaleService_UnshareMachine_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetMachineRoute",
|
||||||
|
Handler: _HeadscaleService_GetMachineRoute_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "EnableMachineRoutes",
|
||||||
|
Handler: _HeadscaleService_EnableMachineRoutes_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "headscale/v1/headscale.proto",
|
||||||
|
}
|
1445
gen/go/headscale/v1/machine.pb.go
Normal file
1445
gen/go/headscale/v1/machine.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
801
gen/go/headscale/v1/namespace.pb.go
Normal file
801
gen/go/headscale/v1/namespace.pb.go
Normal file
@ -0,0 +1,801 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.27.1
|
||||||
|
// protoc v3.18.1
|
||||||
|
// source: headscale/v1/namespace.proto
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Namespace struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Namespace) Reset() {
|
||||||
|
*x = Namespace{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Namespace) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Namespace) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Namespace) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Namespace.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Namespace) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Namespace) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Namespace) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Namespace) GetCreatedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.CreatedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetNamespaceRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetNamespaceRequest) Reset() {
|
||||||
|
*x = GetNamespaceRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetNamespaceRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetNamespaceRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetNamespaceRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetNamespaceRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetNamespaceRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetNamespaceRequest) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetNamespaceResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Namespace *Namespace `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetNamespaceResponse) Reset() {
|
||||||
|
*x = GetNamespaceResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetNamespaceResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetNamespaceResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetNamespaceResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetNamespaceResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetNamespaceResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetNamespaceResponse) GetNamespace() *Namespace {
|
||||||
|
if x != nil {
|
||||||
|
return x.Namespace
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateNamespaceRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateNamespaceRequest) Reset() {
|
||||||
|
*x = CreateNamespaceRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateNamespaceRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CreateNamespaceRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CreateNamespaceRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CreateNamespaceRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CreateNamespaceRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateNamespaceRequest) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateNamespaceResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Namespace *Namespace `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateNamespaceResponse) Reset() {
|
||||||
|
*x = CreateNamespaceResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateNamespaceResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CreateNamespaceResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CreateNamespaceResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[4]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CreateNamespaceResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CreateNamespaceResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateNamespaceResponse) GetNamespace() *Namespace {
|
||||||
|
if x != nil {
|
||||||
|
return x.Namespace
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenameNamespaceRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
OldName string `protobuf:"bytes,1,opt,name=old_name,json=oldName,proto3" json:"old_name,omitempty"`
|
||||||
|
NewName string `protobuf:"bytes,2,opt,name=new_name,json=newName,proto3" json:"new_name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenameNamespaceRequest) Reset() {
|
||||||
|
*x = RenameNamespaceRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenameNamespaceRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RenameNamespaceRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RenameNamespaceRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[5]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RenameNamespaceRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RenameNamespaceRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenameNamespaceRequest) GetOldName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.OldName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenameNamespaceRequest) GetNewName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.NewName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenameNamespaceResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Namespace *Namespace `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenameNamespaceResponse) Reset() {
|
||||||
|
*x = RenameNamespaceResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenameNamespaceResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RenameNamespaceResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RenameNamespaceResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[6]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RenameNamespaceResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RenameNamespaceResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RenameNamespaceResponse) GetNamespace() *Namespace {
|
||||||
|
if x != nil {
|
||||||
|
return x.Namespace
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteNamespaceRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteNamespaceRequest) Reset() {
|
||||||
|
*x = DeleteNamespaceRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteNamespaceRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeleteNamespaceRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeleteNamespaceRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[7]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DeleteNamespaceRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeleteNamespaceRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteNamespaceRequest) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteNamespaceResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteNamespaceResponse) Reset() {
|
||||||
|
*x = DeleteNamespaceResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[8]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DeleteNamespaceResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeleteNamespaceResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DeleteNamespaceResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[8]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use DeleteNamespaceResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DeleteNamespaceResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{8}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListNamespacesRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListNamespacesRequest) Reset() {
|
||||||
|
*x = ListNamespacesRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[9]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListNamespacesRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListNamespacesRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListNamespacesRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[9]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListNamespacesRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListNamespacesRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{9}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListNamespacesResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Namespaces []*Namespace `protobuf:"bytes,1,rep,name=namespaces,proto3" json:"namespaces,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListNamespacesResponse) Reset() {
|
||||||
|
*x = ListNamespacesResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[10]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListNamespacesResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListNamespacesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListNamespacesResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_namespace_proto_msgTypes[10]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListNamespacesResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListNamespacesResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescGZIP(), []int{10}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListNamespacesResponse) GetNamespaces() []*Namespace {
|
||||||
|
if x != nil {
|
||||||
|
return x.Namespaces
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_headscale_v1_namespace_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_headscale_v1_namespace_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x1c, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x6e,
|
||||||
|
0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c,
|
||||||
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f,
|
||||||
|
0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69,
|
||||||
|
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6a, 0x0a,
|
||||||
|
0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||||
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
|
||||||
|
0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x39,
|
||||||
|
0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01,
|
||||||
|
0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09,
|
||||||
|
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x29, 0x0a, 0x13, 0x47, 0x65, 0x74,
|
||||||
|
0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||||
|
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||||
|
0x6e, 0x61, 0x6d, 0x65, 0x22, 0x4d, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73,
|
||||||
|
0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x09,
|
||||||
|
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
|
0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e,
|
||||||
|
0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
|
||||||
|
0x61, 0x63, 0x65, 0x22, 0x2c, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d,
|
||||||
|
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a,
|
||||||
|
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
|
||||||
|
0x65, 0x22, 0x50, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73,
|
||||||
|
0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x09,
|
||||||
|
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
|
0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e,
|
||||||
|
0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
|
||||||
|
0x61, 0x63, 0x65, 0x22, 0x4e, 0x0a, 0x16, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d,
|
||||||
|
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a,
|
||||||
|
0x08, 0x6f, 0x6c, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x07, 0x6f, 0x6c, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f,
|
||||||
|
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x4e,
|
||||||
|
0x61, 0x6d, 0x65, 0x22, 0x50, 0x0a, 0x17, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x4e, 0x61, 0x6d,
|
||||||
|
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35,
|
||||||
|
0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
|
0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31,
|
||||||
|
0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65,
|
||||||
|
0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x2c, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e,
|
||||||
|
0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||||
|
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
|
||||||
|
0x61, 0x6d, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d,
|
||||||
|
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17,
|
||||||
|
0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x51, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x4e,
|
||||||
|
0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
|
0x65, 0x12, 0x37, 0x0a, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18,
|
||||||
|
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c,
|
||||||
|
0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x0a,
|
||||||
|
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69,
|
||||||
|
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e,
|
||||||
|
0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f,
|
||||||
|
0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_headscale_v1_namespace_proto_rawDescOnce sync.Once
|
||||||
|
file_headscale_v1_namespace_proto_rawDescData = file_headscale_v1_namespace_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_headscale_v1_namespace_proto_rawDescGZIP() []byte {
|
||||||
|
file_headscale_v1_namespace_proto_rawDescOnce.Do(func() {
|
||||||
|
file_headscale_v1_namespace_proto_rawDescData = protoimpl.X.CompressGZIP(file_headscale_v1_namespace_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_headscale_v1_namespace_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_headscale_v1_namespace_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
|
||||||
|
var file_headscale_v1_namespace_proto_goTypes = []interface{}{
|
||||||
|
(*Namespace)(nil), // 0: headscale.v1.Namespace
|
||||||
|
(*GetNamespaceRequest)(nil), // 1: headscale.v1.GetNamespaceRequest
|
||||||
|
(*GetNamespaceResponse)(nil), // 2: headscale.v1.GetNamespaceResponse
|
||||||
|
(*CreateNamespaceRequest)(nil), // 3: headscale.v1.CreateNamespaceRequest
|
||||||
|
(*CreateNamespaceResponse)(nil), // 4: headscale.v1.CreateNamespaceResponse
|
||||||
|
(*RenameNamespaceRequest)(nil), // 5: headscale.v1.RenameNamespaceRequest
|
||||||
|
(*RenameNamespaceResponse)(nil), // 6: headscale.v1.RenameNamespaceResponse
|
||||||
|
(*DeleteNamespaceRequest)(nil), // 7: headscale.v1.DeleteNamespaceRequest
|
||||||
|
(*DeleteNamespaceResponse)(nil), // 8: headscale.v1.DeleteNamespaceResponse
|
||||||
|
(*ListNamespacesRequest)(nil), // 9: headscale.v1.ListNamespacesRequest
|
||||||
|
(*ListNamespacesResponse)(nil), // 10: headscale.v1.ListNamespacesResponse
|
||||||
|
(*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
|
||||||
|
}
|
||||||
|
var file_headscale_v1_namespace_proto_depIdxs = []int32{
|
||||||
|
11, // 0: headscale.v1.Namespace.created_at:type_name -> google.protobuf.Timestamp
|
||||||
|
0, // 1: headscale.v1.GetNamespaceResponse.namespace:type_name -> headscale.v1.Namespace
|
||||||
|
0, // 2: headscale.v1.CreateNamespaceResponse.namespace:type_name -> headscale.v1.Namespace
|
||||||
|
0, // 3: headscale.v1.RenameNamespaceResponse.namespace:type_name -> headscale.v1.Namespace
|
||||||
|
0, // 4: headscale.v1.ListNamespacesResponse.namespaces:type_name -> headscale.v1.Namespace
|
||||||
|
5, // [5:5] is the sub-list for method output_type
|
||||||
|
5, // [5:5] is the sub-list for method input_type
|
||||||
|
5, // [5:5] is the sub-list for extension type_name
|
||||||
|
5, // [5:5] is the sub-list for extension extendee
|
||||||
|
0, // [0:5] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_headscale_v1_namespace_proto_init() }
|
||||||
|
func file_headscale_v1_namespace_proto_init() {
|
||||||
|
if File_headscale_v1_namespace_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Namespace); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*GetNamespaceRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*GetNamespaceResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CreateNamespaceRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CreateNamespaceResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*RenameNamespaceRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*RenameNamespaceResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DeleteNamespaceRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*DeleteNamespaceResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ListNamespacesRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_namespace_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ListNamespacesResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_headscale_v1_namespace_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 11,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_headscale_v1_namespace_proto_goTypes,
|
||||||
|
DependencyIndexes: file_headscale_v1_namespace_proto_depIdxs,
|
||||||
|
MessageInfos: file_headscale_v1_namespace_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_headscale_v1_namespace_proto = out.File
|
||||||
|
file_headscale_v1_namespace_proto_rawDesc = nil
|
||||||
|
file_headscale_v1_namespace_proto_goTypes = nil
|
||||||
|
file_headscale_v1_namespace_proto_depIdxs = nil
|
||||||
|
}
|
640
gen/go/headscale/v1/preauthkey.pb.go
Normal file
640
gen/go/headscale/v1/preauthkey.pb.go
Normal file
@ -0,0 +1,640 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.27.1
|
||||||
|
// protoc v3.18.1
|
||||||
|
// source: headscale/v1/preauthkey.proto
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type PreAuthKey struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
|
||||||
|
Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
|
Reusable bool `protobuf:"varint,4,opt,name=reusable,proto3" json:"reusable,omitempty"`
|
||||||
|
Ephemeral bool `protobuf:"varint,5,opt,name=ephemeral,proto3" json:"ephemeral,omitempty"`
|
||||||
|
Used bool `protobuf:"varint,6,opt,name=used,proto3" json:"used,omitempty"`
|
||||||
|
Expiration *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=expiration,proto3" json:"expiration,omitempty"`
|
||||||
|
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) Reset() {
|
||||||
|
*x = PreAuthKey{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*PreAuthKey) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use PreAuthKey.ProtoReflect.Descriptor instead.
|
||||||
|
func (*PreAuthKey) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) GetNamespace() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Namespace
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) GetKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Key
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) GetReusable() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Reusable
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) GetEphemeral() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ephemeral
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) GetUsed() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Used
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) GetExpiration() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.Expiration
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *PreAuthKey) GetCreatedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.CreatedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePreAuthKeyRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
|
||||||
|
Reusable bool `protobuf:"varint,2,opt,name=reusable,proto3" json:"reusable,omitempty"`
|
||||||
|
Ephemeral bool `protobuf:"varint,3,opt,name=ephemeral,proto3" json:"ephemeral,omitempty"`
|
||||||
|
Expiration *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expiration,proto3" json:"expiration,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyRequest) Reset() {
|
||||||
|
*x = CreatePreAuthKeyRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CreatePreAuthKeyRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CreatePreAuthKeyRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CreatePreAuthKeyRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyRequest) GetNamespace() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Namespace
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyRequest) GetReusable() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Reusable
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyRequest) GetEphemeral() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Ephemeral
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyRequest) GetExpiration() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.Expiration
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePreAuthKeyResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
PreAuthKey *PreAuthKey `protobuf:"bytes,1,opt,name=pre_auth_key,json=preAuthKey,proto3" json:"pre_auth_key,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyResponse) Reset() {
|
||||||
|
*x = CreatePreAuthKeyResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CreatePreAuthKeyResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CreatePreAuthKeyResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CreatePreAuthKeyResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreatePreAuthKeyResponse) GetPreAuthKey() *PreAuthKey {
|
||||||
|
if x != nil {
|
||||||
|
return x.PreAuthKey
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpirePreAuthKeyRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
|
||||||
|
Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpirePreAuthKeyRequest) Reset() {
|
||||||
|
*x = ExpirePreAuthKeyRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpirePreAuthKeyRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExpirePreAuthKeyRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ExpirePreAuthKeyRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExpirePreAuthKeyRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ExpirePreAuthKeyRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpirePreAuthKeyRequest) GetNamespace() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Namespace
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpirePreAuthKeyRequest) GetKey() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Key
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExpirePreAuthKeyResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpirePreAuthKeyResponse) Reset() {
|
||||||
|
*x = ExpirePreAuthKeyResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ExpirePreAuthKeyResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ExpirePreAuthKeyResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ExpirePreAuthKeyResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[4]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ExpirePreAuthKeyResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ExpirePreAuthKeyResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListPreAuthKeysRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListPreAuthKeysRequest) Reset() {
|
||||||
|
*x = ListPreAuthKeysRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListPreAuthKeysRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListPreAuthKeysRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListPreAuthKeysRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[5]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListPreAuthKeysRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListPreAuthKeysRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListPreAuthKeysRequest) GetNamespace() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Namespace
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListPreAuthKeysResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
PreAuthKeys []*PreAuthKey `protobuf:"bytes,1,rep,name=pre_auth_keys,json=preAuthKeys,proto3" json:"pre_auth_keys,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListPreAuthKeysResponse) Reset() {
|
||||||
|
*x = ListPreAuthKeysResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListPreAuthKeysResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListPreAuthKeysResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListPreAuthKeysResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_preauthkey_proto_msgTypes[6]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ListPreAuthKeysResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListPreAuthKeysResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_preauthkey_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListPreAuthKeysResponse) GetPreAuthKeys() []*PreAuthKey {
|
||||||
|
if x != nil {
|
||||||
|
return x.PreAuthKeys
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_headscale_v1_preauthkey_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_headscale_v1_preauthkey_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x1d, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x70,
|
||||||
|
0x72, 0x65, 0x61, 0x75, 0x74, 0x68, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||||
|
0x0c, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67,
|
||||||
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74,
|
||||||
|
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x91,
|
||||||
|
0x02, 0x0a, 0x0a, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a,
|
||||||
|
0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||||
|
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||||
|
0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a,
|
||||||
|
0x08, 0x72, 0x65, 0x75, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
|
0x08, 0x72, 0x65, 0x75, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x70, 0x68,
|
||||||
|
0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x65, 0x70,
|
||||||
|
0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x64, 0x18,
|
||||||
|
0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x75, 0x73, 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x0a, 0x65,
|
||||||
|
0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
|
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||||
|
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70,
|
||||||
|
0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74,
|
||||||
|
0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||||
|
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
||||||
|
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
|
||||||
|
0x41, 0x74, 0x22, 0xad, 0x01, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65,
|
||||||
|
0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c,
|
||||||
|
0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
|
0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08,
|
||||||
|
0x72, 0x65, 0x75, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,
|
||||||
|
0x72, 0x65, 0x75, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x70, 0x68, 0x65,
|
||||||
|
0x6d, 0x65, 0x72, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x65, 0x70, 0x68,
|
||||||
|
0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61,
|
||||||
|
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
|
||||||
|
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
|
||||||
|
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69,
|
||||||
|
0x6f, 0x6e, 0x22, 0x56, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x72, 0x65, 0x41,
|
||||||
|
0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a,
|
||||||
|
0x0a, 0x0c, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01,
|
||||||
|
0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65,
|
||||||
|
0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x0a,
|
||||||
|
0x70, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x22, 0x49, 0x0a, 0x17, 0x45, 0x78,
|
||||||
|
0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65,
|
||||||
|
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61,
|
||||||
|
0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70,
|
||||||
|
0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x1a, 0x0a, 0x18, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50,
|
||||||
|
0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
|
0x65, 0x22, 0x36, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68,
|
||||||
|
0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e,
|
||||||
|
0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
||||||
|
0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x57, 0x0a, 0x17, 0x4c, 0x69, 0x73,
|
||||||
|
0x74, 0x50, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||||
|
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x0d, 0x70, 0x72, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68,
|
||||||
|
0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x65,
|
||||||
|
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x65, 0x41, 0x75,
|
||||||
|
0x74, 0x68, 0x4b, 0x65, 0x79, 0x52, 0x0b, 0x70, 0x72, 0x65, 0x41, 0x75, 0x74, 0x68, 0x4b, 0x65,
|
||||||
|
0x79, 0x73, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||||
|
0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f, 0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63,
|
||||||
|
0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_headscale_v1_preauthkey_proto_rawDescOnce sync.Once
|
||||||
|
file_headscale_v1_preauthkey_proto_rawDescData = file_headscale_v1_preauthkey_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_headscale_v1_preauthkey_proto_rawDescGZIP() []byte {
|
||||||
|
file_headscale_v1_preauthkey_proto_rawDescOnce.Do(func() {
|
||||||
|
file_headscale_v1_preauthkey_proto_rawDescData = protoimpl.X.CompressGZIP(file_headscale_v1_preauthkey_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_headscale_v1_preauthkey_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_headscale_v1_preauthkey_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||||
|
var file_headscale_v1_preauthkey_proto_goTypes = []interface{}{
|
||||||
|
(*PreAuthKey)(nil), // 0: headscale.v1.PreAuthKey
|
||||||
|
(*CreatePreAuthKeyRequest)(nil), // 1: headscale.v1.CreatePreAuthKeyRequest
|
||||||
|
(*CreatePreAuthKeyResponse)(nil), // 2: headscale.v1.CreatePreAuthKeyResponse
|
||||||
|
(*ExpirePreAuthKeyRequest)(nil), // 3: headscale.v1.ExpirePreAuthKeyRequest
|
||||||
|
(*ExpirePreAuthKeyResponse)(nil), // 4: headscale.v1.ExpirePreAuthKeyResponse
|
||||||
|
(*ListPreAuthKeysRequest)(nil), // 5: headscale.v1.ListPreAuthKeysRequest
|
||||||
|
(*ListPreAuthKeysResponse)(nil), // 6: headscale.v1.ListPreAuthKeysResponse
|
||||||
|
(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
|
||||||
|
}
|
||||||
|
var file_headscale_v1_preauthkey_proto_depIdxs = []int32{
|
||||||
|
7, // 0: headscale.v1.PreAuthKey.expiration:type_name -> google.protobuf.Timestamp
|
||||||
|
7, // 1: headscale.v1.PreAuthKey.created_at:type_name -> google.protobuf.Timestamp
|
||||||
|
7, // 2: headscale.v1.CreatePreAuthKeyRequest.expiration:type_name -> google.protobuf.Timestamp
|
||||||
|
0, // 3: headscale.v1.CreatePreAuthKeyResponse.pre_auth_key:type_name -> headscale.v1.PreAuthKey
|
||||||
|
0, // 4: headscale.v1.ListPreAuthKeysResponse.pre_auth_keys:type_name -> headscale.v1.PreAuthKey
|
||||||
|
5, // [5:5] is the sub-list for method output_type
|
||||||
|
5, // [5:5] is the sub-list for method input_type
|
||||||
|
5, // [5:5] is the sub-list for extension type_name
|
||||||
|
5, // [5:5] is the sub-list for extension extendee
|
||||||
|
0, // [0:5] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_headscale_v1_preauthkey_proto_init() }
|
||||||
|
func file_headscale_v1_preauthkey_proto_init() {
|
||||||
|
if File_headscale_v1_preauthkey_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_headscale_v1_preauthkey_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*PreAuthKey); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_preauthkey_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CreatePreAuthKeyRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_preauthkey_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CreatePreAuthKeyResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_preauthkey_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ExpirePreAuthKeyRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_preauthkey_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ExpirePreAuthKeyResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_preauthkey_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ListPreAuthKeysRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_preauthkey_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*ListPreAuthKeysResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_headscale_v1_preauthkey_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 7,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_headscale_v1_preauthkey_proto_goTypes,
|
||||||
|
DependencyIndexes: file_headscale_v1_preauthkey_proto_depIdxs,
|
||||||
|
MessageInfos: file_headscale_v1_preauthkey_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_headscale_v1_preauthkey_proto = out.File
|
||||||
|
file_headscale_v1_preauthkey_proto_rawDesc = nil
|
||||||
|
file_headscale_v1_preauthkey_proto_goTypes = nil
|
||||||
|
file_headscale_v1_preauthkey_proto_depIdxs = nil
|
||||||
|
}
|
424
gen/go/headscale/v1/routes.pb.go
Normal file
424
gen/go/headscale/v1/routes.pb.go
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.27.1
|
||||||
|
// protoc v3.18.1
|
||||||
|
// source: headscale/v1/routes.proto
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Routes struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
AdvertisedRoutes []string `protobuf:"bytes,1,rep,name=advertised_routes,json=advertisedRoutes,proto3" json:"advertised_routes,omitempty"`
|
||||||
|
EnabledRoutes []string `protobuf:"bytes,2,rep,name=enabled_routes,json=enabledRoutes,proto3" json:"enabled_routes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Routes) Reset() {
|
||||||
|
*x = Routes{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_routes_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Routes) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Routes) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Routes) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_routes_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Routes.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Routes) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_routes_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Routes) GetAdvertisedRoutes() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AdvertisedRoutes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Routes) GetEnabledRoutes() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.EnabledRoutes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetMachineRouteRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
MachineId uint64 `protobuf:"varint,1,opt,name=machine_id,json=machineId,proto3" json:"machine_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetMachineRouteRequest) Reset() {
|
||||||
|
*x = GetMachineRouteRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_routes_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetMachineRouteRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetMachineRouteRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetMachineRouteRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_routes_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetMachineRouteRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetMachineRouteRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_routes_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetMachineRouteRequest) GetMachineId() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.MachineId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetMachineRouteResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Routes *Routes `protobuf:"bytes,1,opt,name=routes,proto3" json:"routes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetMachineRouteResponse) Reset() {
|
||||||
|
*x = GetMachineRouteResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_routes_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetMachineRouteResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetMachineRouteResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetMachineRouteResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_routes_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use GetMachineRouteResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetMachineRouteResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_routes_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetMachineRouteResponse) GetRoutes() *Routes {
|
||||||
|
if x != nil {
|
||||||
|
return x.Routes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnableMachineRoutesRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
MachineId uint64 `protobuf:"varint,1,opt,name=machine_id,json=machineId,proto3" json:"machine_id,omitempty"`
|
||||||
|
Routes []string `protobuf:"bytes,2,rep,name=routes,proto3" json:"routes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EnableMachineRoutesRequest) Reset() {
|
||||||
|
*x = EnableMachineRoutesRequest{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_routes_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EnableMachineRoutesRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*EnableMachineRoutesRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *EnableMachineRoutesRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_routes_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use EnableMachineRoutesRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*EnableMachineRoutesRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_routes_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EnableMachineRoutesRequest) GetMachineId() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.MachineId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EnableMachineRoutesRequest) GetRoutes() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Routes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnableMachineRoutesResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Routes *Routes `protobuf:"bytes,1,opt,name=routes,proto3" json:"routes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EnableMachineRoutesResponse) Reset() {
|
||||||
|
*x = EnableMachineRoutesResponse{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_headscale_v1_routes_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EnableMachineRoutesResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*EnableMachineRoutesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *EnableMachineRoutesResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_headscale_v1_routes_proto_msgTypes[4]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use EnableMachineRoutesResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*EnableMachineRoutesResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_headscale_v1_routes_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EnableMachineRoutesResponse) GetRoutes() *Routes {
|
||||||
|
if x != nil {
|
||||||
|
return x.Routes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_headscale_v1_routes_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_headscale_v1_routes_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x19, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x72,
|
||||||
|
0x6f, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x68, 0x65, 0x61,
|
||||||
|
0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x22, 0x5c, 0x0a, 0x06, 0x52, 0x6f, 0x75,
|
||||||
|
0x74, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x65,
|
||||||
|
0x64, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10,
|
||||||
|
0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73,
|
||||||
|
0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x74,
|
||||||
|
0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
||||||
|
0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x37, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4d, 0x61,
|
||||||
|
0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
|
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
|
||||||
|
0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
|
||||||
|
0x22, 0x47, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f,
|
||||||
|
0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x06, 0x72,
|
||||||
|
0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x68, 0x65,
|
||||||
|
0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65,
|
||||||
|
0x73, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x53, 0x0a, 0x1a, 0x45, 0x6e, 0x61,
|
||||||
|
0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73,
|
||||||
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69,
|
||||||
|
0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6d, 0x61, 0x63,
|
||||||
|
0x68, 0x69, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73,
|
||||||
|
0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x4b,
|
||||||
|
0x0a, 0x1b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x52,
|
||||||
|
0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a,
|
||||||
|
0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e,
|
||||||
|
0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75,
|
||||||
|
0x74, 0x65, 0x73, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x42, 0x29, 0x5a, 0x27, 0x67,
|
||||||
|
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6a, 0x75, 0x61, 0x6e, 0x66, 0x6f,
|
||||||
|
0x6e, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x2f, 0x67, 0x65, 0x6e,
|
||||||
|
0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_headscale_v1_routes_proto_rawDescOnce sync.Once
|
||||||
|
file_headscale_v1_routes_proto_rawDescData = file_headscale_v1_routes_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_headscale_v1_routes_proto_rawDescGZIP() []byte {
|
||||||
|
file_headscale_v1_routes_proto_rawDescOnce.Do(func() {
|
||||||
|
file_headscale_v1_routes_proto_rawDescData = protoimpl.X.CompressGZIP(file_headscale_v1_routes_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_headscale_v1_routes_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_headscale_v1_routes_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
|
var file_headscale_v1_routes_proto_goTypes = []interface{}{
|
||||||
|
(*Routes)(nil), // 0: headscale.v1.Routes
|
||||||
|
(*GetMachineRouteRequest)(nil), // 1: headscale.v1.GetMachineRouteRequest
|
||||||
|
(*GetMachineRouteResponse)(nil), // 2: headscale.v1.GetMachineRouteResponse
|
||||||
|
(*EnableMachineRoutesRequest)(nil), // 3: headscale.v1.EnableMachineRoutesRequest
|
||||||
|
(*EnableMachineRoutesResponse)(nil), // 4: headscale.v1.EnableMachineRoutesResponse
|
||||||
|
}
|
||||||
|
var file_headscale_v1_routes_proto_depIdxs = []int32{
|
||||||
|
0, // 0: headscale.v1.GetMachineRouteResponse.routes:type_name -> headscale.v1.Routes
|
||||||
|
0, // 1: headscale.v1.EnableMachineRoutesResponse.routes:type_name -> headscale.v1.Routes
|
||||||
|
2, // [2:2] is the sub-list for method output_type
|
||||||
|
2, // [2:2] is the sub-list for method input_type
|
||||||
|
2, // [2:2] is the sub-list for extension type_name
|
||||||
|
2, // [2:2] is the sub-list for extension extendee
|
||||||
|
0, // [0:2] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_headscale_v1_routes_proto_init() }
|
||||||
|
func file_headscale_v1_routes_proto_init() {
|
||||||
|
if File_headscale_v1_routes_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_headscale_v1_routes_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Routes); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_routes_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*GetMachineRouteRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_routes_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*GetMachineRouteResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_routes_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*EnableMachineRoutesRequest); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_headscale_v1_routes_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*EnableMachineRoutesResponse); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_headscale_v1_routes_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 5,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_headscale_v1_routes_proto_goTypes,
|
||||||
|
DependencyIndexes: file_headscale_v1_routes_proto_depIdxs,
|
||||||
|
MessageInfos: file_headscale_v1_routes_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_headscale_v1_routes_proto = out.File
|
||||||
|
file_headscale_v1_routes_proto_rawDesc = nil
|
||||||
|
file_headscale_v1_routes_proto_goTypes = nil
|
||||||
|
file_headscale_v1_routes_proto_depIdxs = nil
|
||||||
|
}
|
43
gen/openapiv2/headscale/v1/device.swagger.json
Normal file
43
gen/openapiv2/headscale/v1/device.swagger.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"title": "headscale/v1/device.proto",
|
||||||
|
"version": "version not set"
|
||||||
|
},
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"paths": {},
|
||||||
|
"definitions": {
|
||||||
|
"protobufAny": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": {}
|
||||||
|
},
|
||||||
|
"rpcStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protobufAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
920
gen/openapiv2/headscale/v1/headscale.swagger.json
Normal file
920
gen/openapiv2/headscale/v1/headscale.swagger.json
Normal file
@ -0,0 +1,920 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"title": "headscale/v1/headscale.proto",
|
||||||
|
"version": "version not set"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
{
|
||||||
|
"name": "HeadscaleService"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/api/v1/debug/machine": {
|
||||||
|
"post": {
|
||||||
|
"summary": "--- Machine start ---",
|
||||||
|
"operationId": "HeadscaleService_DebugCreateMachine",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1DebugCreateMachineResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1DebugCreateMachineRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/machine": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "HeadscaleService_ListMachines",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1ListMachinesResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/machine/register": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "HeadscaleService_RegisterMachine",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1RegisterMachineResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/machine/{machineId}": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "HeadscaleService_GetMachine",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1GetMachineResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "machineId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"operationId": "HeadscaleService_DeleteMachine",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1DeleteMachineResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "machineId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/machine/{machineId}/expire": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "HeadscaleService_ExpireMachine",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1ExpireMachineResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "machineId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/machine/{machineId}/routes": {
|
||||||
|
"get": {
|
||||||
|
"summary": "--- Route start ---",
|
||||||
|
"operationId": "HeadscaleService_GetMachineRoute",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1GetMachineRouteResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "machineId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"operationId": "HeadscaleService_EnableMachineRoutes",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1EnableMachineRoutesResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "machineId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/machine/{machineId}/share/{namespace}": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "HeadscaleService_ShareMachine",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1ShareMachineResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "machineId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/machine/{machineId}/unshare/{namespace}": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "HeadscaleService_UnshareMachine",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1UnshareMachineResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "machineId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/namespace": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "HeadscaleService_ListNamespaces",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1ListNamespacesResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"operationId": "HeadscaleService_CreateNamespace",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1CreateNamespaceResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1CreateNamespaceRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/namespace/{name}": {
|
||||||
|
"get": {
|
||||||
|
"summary": "--- Namespace start ---",
|
||||||
|
"operationId": "HeadscaleService_GetNamespace",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1GetNamespaceResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"operationId": "HeadscaleService_DeleteNamespace",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1DeleteNamespaceResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/namespace/{oldName}/rename/{newName}": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "HeadscaleService_RenameNamespace",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1RenameNamespaceResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "oldName",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newName",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/preauthkey": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "HeadscaleService_ListPreAuthKeys",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1ListPreAuthKeysResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "namespace",
|
||||||
|
"in": "query",
|
||||||
|
"required": false,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"summary": "--- PreAuthKeys start ---",
|
||||||
|
"operationId": "HeadscaleService_CreatePreAuthKey",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1CreatePreAuthKeyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1CreatePreAuthKeyRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/preauthkey/expire": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "HeadscaleService_ExpirePreAuthKey",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1ExpirePreAuthKeyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1ExpirePreAuthKeyRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"HeadscaleService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"protobufAny": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": {}
|
||||||
|
},
|
||||||
|
"rpcStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protobufAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1CreateNamespaceRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1CreateNamespaceResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"namespace": {
|
||||||
|
"$ref": "#/definitions/v1Namespace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1CreatePreAuthKeyRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"namespace": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"reusable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"ephemeral": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"expiration": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1CreatePreAuthKeyResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"preAuthKey": {
|
||||||
|
"$ref": "#/definitions/v1PreAuthKey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1DebugCreateMachineRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"namespace": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"routes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1DebugCreateMachineResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"machine": {
|
||||||
|
"$ref": "#/definitions/v1Machine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1DeleteMachineResponse": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"v1DeleteNamespaceResponse": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"v1EnableMachineRoutesResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"routes": {
|
||||||
|
"$ref": "#/definitions/v1Routes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1ExpireMachineResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"machine": {
|
||||||
|
"$ref": "#/definitions/v1Machine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1ExpirePreAuthKeyRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"namespace": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1ExpirePreAuthKeyResponse": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"v1GetMachineResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"machine": {
|
||||||
|
"$ref": "#/definitions/v1Machine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1GetMachineRouteResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"routes": {
|
||||||
|
"$ref": "#/definitions/v1Routes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1GetNamespaceResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"namespace": {
|
||||||
|
"$ref": "#/definitions/v1Namespace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1ListMachinesResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"machines": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/v1Machine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1ListNamespacesResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"namespaces": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/v1Namespace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1ListPreAuthKeysResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"preAuthKeys": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/v1PreAuthKey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1Machine": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
"machineKey": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"nodeKey": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"discoKey": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"ipAddress": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"namespace": {
|
||||||
|
"$ref": "#/definitions/v1Namespace"
|
||||||
|
},
|
||||||
|
"registered": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"registerMethod": {
|
||||||
|
"$ref": "#/definitions/v1RegisterMethod"
|
||||||
|
},
|
||||||
|
"lastSeen": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"lastSuccessfulUpdate": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"expiry": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"preAuthKey": {
|
||||||
|
"$ref": "#/definitions/v1PreAuthKey"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1Namespace": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1PreAuthKey": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"namespace": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"reusable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"ephemeral": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"used": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"expiration": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1RegisterMachineResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"machine": {
|
||||||
|
"$ref": "#/definitions/v1Machine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1RegisterMethod": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"REGISTER_METHOD_UNSPECIFIED",
|
||||||
|
"REGISTER_METHOD_AUTH_KEY",
|
||||||
|
"REGISTER_METHOD_CLI",
|
||||||
|
"REGISTER_METHOD_OIDC"
|
||||||
|
],
|
||||||
|
"default": "REGISTER_METHOD_UNSPECIFIED"
|
||||||
|
},
|
||||||
|
"v1RenameNamespaceResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"namespace": {
|
||||||
|
"$ref": "#/definitions/v1Namespace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1Routes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"advertisedRoutes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enabledRoutes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1ShareMachineResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"machine": {
|
||||||
|
"$ref": "#/definitions/v1Machine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1UnshareMachineResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"machine": {
|
||||||
|
"$ref": "#/definitions/v1Machine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
gen/openapiv2/headscale/v1/machine.swagger.json
Normal file
43
gen/openapiv2/headscale/v1/machine.swagger.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"title": "headscale/v1/machine.proto",
|
||||||
|
"version": "version not set"
|
||||||
|
},
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"paths": {},
|
||||||
|
"definitions": {
|
||||||
|
"protobufAny": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": {}
|
||||||
|
},
|
||||||
|
"rpcStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protobufAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
gen/openapiv2/headscale/v1/namespace.swagger.json
Normal file
43
gen/openapiv2/headscale/v1/namespace.swagger.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"title": "headscale/v1/namespace.proto",
|
||||||
|
"version": "version not set"
|
||||||
|
},
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"paths": {},
|
||||||
|
"definitions": {
|
||||||
|
"protobufAny": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": {}
|
||||||
|
},
|
||||||
|
"rpcStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protobufAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
gen/openapiv2/headscale/v1/preauthkey.swagger.json
Normal file
43
gen/openapiv2/headscale/v1/preauthkey.swagger.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"title": "headscale/v1/preauthkey.proto",
|
||||||
|
"version": "version not set"
|
||||||
|
},
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"paths": {},
|
||||||
|
"definitions": {
|
||||||
|
"protobufAny": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": {}
|
||||||
|
},
|
||||||
|
"rpcStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protobufAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
gen/openapiv2/headscale/v1/routes.swagger.json
Normal file
43
gen/openapiv2/headscale/v1/routes.swagger.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"title": "headscale/v1/routes.proto",
|
||||||
|
"version": "version not set"
|
||||||
|
},
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"paths": {},
|
||||||
|
"definitions": {
|
||||||
|
"protobufAny": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"@type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": {}
|
||||||
|
},
|
||||||
|
"rpcStatus": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/protobufAny"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
141
go.mod
141
go.mod
@ -1,45 +1,138 @@
|
|||||||
module github.com/juanfont/headscale
|
module github.com/juanfont/headscale
|
||||||
|
|
||||||
go 1.16
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.2
|
github.com/AlecAivazis/survey/v2 v2.3.2
|
||||||
github.com/Microsoft/go-winio v0.5.0 // indirect
|
github.com/coreos/go-oidc/v3 v3.1.0
|
||||||
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
|
|
||||||
github.com/containerd/continuity v0.1.0 // indirect
|
|
||||||
github.com/docker/cli v20.10.8+incompatible // indirect
|
|
||||||
github.com/docker/docker v20.10.8+incompatible // indirect
|
|
||||||
github.com/efekarakus/termcolor v1.0.1
|
github.com/efekarakus/termcolor v1.0.1
|
||||||
github.com/fatih/set v0.2.1 // indirect
|
github.com/fatih/set v0.2.1
|
||||||
github.com/gin-gonic/gin v1.7.4
|
github.com/gin-gonic/gin v1.7.4
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible
|
github.com/gofrs/uuid v4.1.0+incompatible
|
||||||
github.com/google/go-github v17.0.0+incompatible // indirect
|
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0
|
||||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
github.com/infobloxopen/protoc-gen-gorm v1.0.1
|
||||||
github.com/klauspost/compress v1.13.5
|
github.com/klauspost/compress v1.13.6
|
||||||
github.com/lib/pq v1.10.3 // indirect
|
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
|
||||||
github.com/opencontainers/runc v1.0.2 // indirect
|
|
||||||
github.com/ory/dockertest/v3 v3.7.0
|
github.com/ory/dockertest/v3 v3.7.0
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
|
github.com/philip-bui/grpc-zerolog v1.0.1
|
||||||
github.com/prometheus/client_golang v1.11.0
|
github.com/prometheus/client_golang v1.11.0
|
||||||
github.com/pterm/pterm v0.12.30
|
github.com/pterm/pterm v0.12.30
|
||||||
github.com/rs/zerolog v1.25.0
|
github.com/rs/zerolog v1.26.0
|
||||||
|
github.com/soheilhy/cmux v0.1.5
|
||||||
github.com/spf13/cobra v1.2.1
|
github.com/spf13/cobra v1.2.1
|
||||||
github.com/spf13/viper v1.8.1
|
github.com/spf13/viper v1.9.0
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/tailscale/hujson v0.0.0-20210818175511-7360507a6e88
|
github.com/tailscale/hujson v0.0.0-20211105212140-3a0adc019d83
|
||||||
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
|
||||||
github.com/zsais/go-gin-prometheus v0.1.0
|
github.com/zsais/go-gin-prometheus v0.1.0
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e
|
||||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e // indirect
|
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
|
||||||
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
|
google.golang.org/genproto v0.0.0-20211104193956-4c6863e31247
|
||||||
|
google.golang.org/grpc v1.42.0
|
||||||
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
|
||||||
|
google.golang.org/protobuf v1.27.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.v2 v2.4.0
|
||||||
gorm.io/datatypes v1.0.2
|
gorm.io/datatypes v1.0.2
|
||||||
gorm.io/driver/postgres v1.1.1
|
gorm.io/driver/postgres v1.1.1
|
||||||
gorm.io/driver/sqlite v1.1.5
|
gorm.io/driver/sqlite v1.1.5
|
||||||
gorm.io/gorm v1.21.15
|
gorm.io/gorm v1.21.15
|
||||||
inet.af/netaddr v0.0.0-20210903134321-85fa6c94624e
|
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
|
||||||
tailscale.com v1.14.2
|
tailscale.com v1.20.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||||
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||||
|
github.com/atomicgo/cursor v0.0.1 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
|
github.com/containerd/continuity v0.1.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/docker/cli v20.10.8+incompatible // indirect
|
||||||
|
github.com/docker/docker v20.10.8+incompatible // indirect
|
||||||
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||||
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.9.0 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/glog v1.0.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/google/go-github v17.0.0+incompatible // indirect
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
|
github.com/gookit/color v1.4.2 // indirect
|
||||||
|
github.com/hashicorp/go-version v1.2.0 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
|
github.com/jackc/pgconn v1.10.0 // indirect
|
||||||
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgproto3/v2 v2.1.1 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||||
|
github.com/jackc/pgtype v1.8.1 // indirect
|
||||||
|
github.com/jackc/pgx/v4 v4.13.0 // indirect
|
||||||
|
github.com/jinzhu/gorm v1.9.16 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.2 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
|
github.com/kr/pretty v0.3.0 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
|
github.com/lib/pq v1.10.3 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.5 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.8 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||||
|
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
|
github.com/opencontainers/runc v1.0.3 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
|
github.com/prometheus/common v0.32.1 // indirect
|
||||||
|
github.com/prometheus/procfs v0.7.3 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.8.1-0.20211023094830-115ce09fd6b4 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
|
github.com/spf13/afero v1.6.0 // indirect
|
||||||
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/subosito/gotenv v1.2.0 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.6 // indirect
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||||
|
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||||
|
go4.org/mem v0.0.0-20210711025021-927187094b94 // indirect
|
||||||
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20211205041911-012df41ee64c // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||||
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
)
|
)
|
||||||
|
407
grpcv1.go
Normal file
407
grpcv1.go
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
//nolint
|
||||||
|
package headscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"gorm.io/datatypes"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type headscaleV1APIServer struct { // v1.HeadscaleServiceServer
|
||||||
|
v1.UnimplementedHeadscaleServiceServer
|
||||||
|
h *Headscale
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHeadscaleV1APIServer(h *Headscale) v1.HeadscaleServiceServer {
|
||||||
|
return headscaleV1APIServer{
|
||||||
|
h: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) GetNamespace(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.GetNamespaceRequest,
|
||||||
|
) (*v1.GetNamespaceResponse, error) {
|
||||||
|
namespace, err := api.h.GetNamespace(request.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.GetNamespaceResponse{Namespace: namespace.toProto()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) CreateNamespace(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.CreateNamespaceRequest,
|
||||||
|
) (*v1.CreateNamespaceResponse, error) {
|
||||||
|
namespace, err := api.h.CreateNamespace(request.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.CreateNamespaceResponse{Namespace: namespace.toProto()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) RenameNamespace(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.RenameNamespaceRequest,
|
||||||
|
) (*v1.RenameNamespaceResponse, error) {
|
||||||
|
err := api.h.RenameNamespace(request.GetOldName(), request.GetNewName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace, err := api.h.GetNamespace(request.GetNewName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.RenameNamespaceResponse{Namespace: namespace.toProto()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) DeleteNamespace(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.DeleteNamespaceRequest,
|
||||||
|
) (*v1.DeleteNamespaceResponse, error) {
|
||||||
|
err := api.h.DestroyNamespace(request.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.DeleteNamespaceResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) ListNamespaces(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.ListNamespacesRequest,
|
||||||
|
) (*v1.ListNamespacesResponse, error) {
|
||||||
|
namespaces, err := api.h.ListNamespaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := make([]*v1.Namespace, len(namespaces))
|
||||||
|
for index, namespace := range namespaces {
|
||||||
|
response[index] = namespace.toProto()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace().Caller().Interface("namespaces", response).Msg("")
|
||||||
|
|
||||||
|
return &v1.ListNamespacesResponse{Namespaces: response}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) CreatePreAuthKey(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.CreatePreAuthKeyRequest,
|
||||||
|
) (*v1.CreatePreAuthKeyResponse, error) {
|
||||||
|
var expiration time.Time
|
||||||
|
if request.GetExpiration() != nil {
|
||||||
|
expiration = request.GetExpiration().AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
preAuthKey, err := api.h.CreatePreAuthKey(
|
||||||
|
request.GetNamespace(),
|
||||||
|
request.GetReusable(),
|
||||||
|
request.GetEphemeral(),
|
||||||
|
&expiration,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.CreatePreAuthKeyResponse{PreAuthKey: preAuthKey.toProto()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) ExpirePreAuthKey(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.ExpirePreAuthKeyRequest,
|
||||||
|
) (*v1.ExpirePreAuthKeyResponse, error) {
|
||||||
|
preAuthKey, err := api.h.GetPreAuthKey(request.GetNamespace(), request.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.h.ExpirePreAuthKey(preAuthKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.ExpirePreAuthKeyResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) ListPreAuthKeys(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.ListPreAuthKeysRequest,
|
||||||
|
) (*v1.ListPreAuthKeysResponse, error) {
|
||||||
|
preAuthKeys, err := api.h.ListPreAuthKeys(request.GetNamespace())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := make([]*v1.PreAuthKey, len(preAuthKeys))
|
||||||
|
for index, key := range preAuthKeys {
|
||||||
|
response[index] = key.toProto()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.ListPreAuthKeysResponse{PreAuthKeys: response}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) RegisterMachine(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.RegisterMachineRequest,
|
||||||
|
) (*v1.RegisterMachineResponse, error) {
|
||||||
|
log.Trace().
|
||||||
|
Str("namespace", request.GetNamespace()).
|
||||||
|
Str("machine_key", request.GetKey()).
|
||||||
|
Msg("Registering machine")
|
||||||
|
machine, err := api.h.RegisterMachine(
|
||||||
|
request.GetKey(),
|
||||||
|
request.GetNamespace(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.RegisterMachineResponse{Machine: machine.toProto()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) GetMachine(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.GetMachineRequest,
|
||||||
|
) (*v1.GetMachineResponse, error) {
|
||||||
|
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.GetMachineResponse{Machine: machine.toProto()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) DeleteMachine(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.DeleteMachineRequest,
|
||||||
|
) (*v1.DeleteMachineResponse, error) {
|
||||||
|
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.h.DeleteMachine(
|
||||||
|
machine,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.DeleteMachineResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) ExpireMachine(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.ExpireMachineRequest,
|
||||||
|
) (*v1.ExpireMachineResponse, error) {
|
||||||
|
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
api.h.ExpireMachine(
|
||||||
|
machine,
|
||||||
|
)
|
||||||
|
|
||||||
|
log.Trace().
|
||||||
|
Str("machine", machine.Name).
|
||||||
|
Time("expiry", *machine.Expiry).
|
||||||
|
Msg("machine expired")
|
||||||
|
|
||||||
|
return &v1.ExpireMachineResponse{Machine: machine.toProto()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) ListMachines(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.ListMachinesRequest,
|
||||||
|
) (*v1.ListMachinesResponse, error) {
|
||||||
|
if request.GetNamespace() != "" {
|
||||||
|
machines, err := api.h.ListMachinesInNamespace(request.GetNamespace())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedMachines, err := api.h.ListSharedMachinesInNamespace(
|
||||||
|
request.GetNamespace(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
machines = append(machines, sharedMachines...)
|
||||||
|
|
||||||
|
response := make([]*v1.Machine, len(machines))
|
||||||
|
for index, machine := range machines {
|
||||||
|
response[index] = machine.toProto()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.ListMachinesResponse{Machines: response}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
machines, err := api.h.ListMachines()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := make([]*v1.Machine, len(machines))
|
||||||
|
for index, machine := range machines {
|
||||||
|
response[index] = machine.toProto()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.ListMachinesResponse{Machines: response}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) ShareMachine(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.ShareMachineRequest,
|
||||||
|
) (*v1.ShareMachineResponse, error) {
|
||||||
|
destinationNamespace, err := api.h.GetNamespace(request.GetNamespace())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.h.AddSharedMachineToNamespace(machine, destinationNamespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.ShareMachineResponse{Machine: machine.toProto()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) UnshareMachine(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.UnshareMachineRequest,
|
||||||
|
) (*v1.UnshareMachineResponse, error) {
|
||||||
|
destinationNamespace, err := api.h.GetNamespace(request.GetNamespace())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.h.RemoveSharedMachineFromNamespace(machine, destinationNamespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.UnshareMachineResponse{Machine: machine.toProto()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) GetMachineRoute(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.GetMachineRouteRequest,
|
||||||
|
) (*v1.GetMachineRouteResponse, error) {
|
||||||
|
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
routes, err := machine.RoutesToProto()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.GetMachineRouteResponse{
|
||||||
|
Routes: routes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) EnableMachineRoutes(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.EnableMachineRoutesRequest,
|
||||||
|
) (*v1.EnableMachineRoutesResponse, error) {
|
||||||
|
machine, err := api.h.GetMachineByID(request.GetMachineId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.h.EnableRoutes(machine, request.GetRoutes()...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
routes, err := machine.RoutesToProto()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.EnableMachineRoutesResponse{
|
||||||
|
Routes: routes,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following service calls are for testing and debugging
|
||||||
|
func (api headscaleV1APIServer) DebugCreateMachine(
|
||||||
|
ctx context.Context,
|
||||||
|
request *v1.DebugCreateMachineRequest,
|
||||||
|
) (*v1.DebugCreateMachineResponse, error) {
|
||||||
|
namespace, err := api.h.GetNamespace(request.GetNamespace())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
routes, err := stringToIPPrefix(request.GetRoutes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace().
|
||||||
|
Caller().
|
||||||
|
Interface("route-prefix", routes).
|
||||||
|
Interface("route-str", request.GetRoutes()).
|
||||||
|
Msg("")
|
||||||
|
|
||||||
|
hostinfo := tailcfg.Hostinfo{
|
||||||
|
RoutableIPs: routes,
|
||||||
|
OS: "TestOS",
|
||||||
|
Hostname: "DebugTestMachine",
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace().Caller().Interface("hostinfo", hostinfo).Msg("")
|
||||||
|
|
||||||
|
hostinfoJson, err := json.Marshal(hostinfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newMachine := Machine{
|
||||||
|
MachineKey: request.GetKey(),
|
||||||
|
Name: request.GetName(),
|
||||||
|
Namespace: *namespace,
|
||||||
|
|
||||||
|
Expiry: &time.Time{},
|
||||||
|
LastSeen: &time.Time{},
|
||||||
|
LastSuccessfulUpdate: &time.Time{},
|
||||||
|
|
||||||
|
HostInfo: datatypes.JSON(hostinfoJson),
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.Trace().Caller().Interface("machine", newMachine).Msg("")
|
||||||
|
|
||||||
|
if err := api.h.db.Create(&newMachine).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.DebugCreateMachineResponse{Machine: newMachine.toProto()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
|
1195
integration_cli_test.go
Normal file
1195
integration_cli_test.go
Normal file
File diff suppressed because it is too large
Load Diff
77
integration_common_test.go
Normal file
77
integration_common_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
//go:build integration
|
||||||
|
// +build integration
|
||||||
|
|
||||||
|
package headscale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ory/dockertest/v3"
|
||||||
|
"github.com/ory/dockertest/v3/docker"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
|
||||||
|
|
||||||
|
func ExecuteCommand(
|
||||||
|
resource *dockertest.Resource,
|
||||||
|
cmd []string,
|
||||||
|
env []string,
|
||||||
|
) (string, error) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
|
// TODO(kradalby): Make configurable
|
||||||
|
timeout := DOCKER_EXECUTE_TIMEOUT
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
exitCode int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
resultChan := make(chan result, 1)
|
||||||
|
|
||||||
|
// Run your long running function in it's own goroutine and pass back it's
|
||||||
|
// response into our channel.
|
||||||
|
go func() {
|
||||||
|
exitCode, err := resource.Exec(
|
||||||
|
cmd,
|
||||||
|
dockertest.ExecOptions{
|
||||||
|
Env: append(env, "HEADSCALE_LOG_LEVEL=disabled"),
|
||||||
|
StdOut: &stdout,
|
||||||
|
StdErr: &stderr,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
resultChan <- result{exitCode, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Listen on our channel AND a timeout channel - which ever happens first.
|
||||||
|
select {
|
||||||
|
case res := <-resultChan:
|
||||||
|
if res.err != nil {
|
||||||
|
return "", res.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.exitCode != 0 {
|
||||||
|
fmt.Println("Command: ", cmd)
|
||||||
|
fmt.Println("stdout: ", stdout.String())
|
||||||
|
fmt.Println("stderr: ", stderr.String())
|
||||||
|
|
||||||
|
return "", fmt.Errorf("command failed with: %s", stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout.String(), nil
|
||||||
|
case <-time.After(timeout):
|
||||||
|
|
||||||
|
return "", fmt.Errorf("command timed out after %s", timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func DockerRestartPolicy(config *docker.HostConfig) {
|
||||||
|
// set AutoRemove to true so that stopped container goes away by itself
|
||||||
|
config.AutoRemove = true
|
||||||
|
config.RestartPolicy = docker.RestartPolicy{
|
||||||
|
Name: "no",
|
||||||
|
}
|
||||||
|
}
|
@ -18,28 +18,17 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
"github.com/ory/dockertest/v3"
|
"github.com/ory/dockertest/v3"
|
||||||
"github.com/ory/dockertest/v3/docker"
|
"github.com/ory/dockertest/v3/docker"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
"inet.af/netaddr"
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
|
|
||||||
"inet.af/netaddr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var tailscaleVersions = []string{"1.20.2", "1.18.2", "1.16.2", "1.14.3", "1.12.3"}
|
||||||
integrationTmpDir string
|
|
||||||
ih Headscale
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
pool dockertest.Pool
|
|
||||||
network dockertest.Network
|
|
||||||
headscale dockertest.Resource
|
|
||||||
)
|
|
||||||
|
|
||||||
var tailscaleVersions = []string{"1.14.3", "1.12.3"}
|
|
||||||
|
|
||||||
type TestNamespace struct {
|
type TestNamespace struct {
|
||||||
count int
|
count int
|
||||||
@ -50,6 +39,10 @@ type IntegrationTestSuite struct {
|
|||||||
suite.Suite
|
suite.Suite
|
||||||
stats *suite.SuiteInformation
|
stats *suite.SuiteInformation
|
||||||
|
|
||||||
|
pool dockertest.Pool
|
||||||
|
network dockertest.Network
|
||||||
|
headscale dockertest.Resource
|
||||||
|
|
||||||
namespaces map[string]TestNamespace
|
namespaces map[string]TestNamespace
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,54 +67,31 @@ func TestIntegrationTestSuite(t *testing.T) {
|
|||||||
// we have potentially saved the logs.
|
// we have potentially saved the logs.
|
||||||
for _, scales := range s.namespaces {
|
for _, scales := range s.namespaces {
|
||||||
for _, tailscale := range scales.tailscales {
|
for _, tailscale := range scales.tailscales {
|
||||||
if err := pool.Purge(&tailscale); err != nil {
|
if err := s.pool.Purge(&tailscale); err != nil {
|
||||||
log.Printf("Could not purge resource: %s\n", err)
|
log.Printf("Could not purge resource: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.stats.Passed() {
|
if !s.stats.Passed() {
|
||||||
err := saveLog(&headscale, "test_output")
|
err := s.saveLog(&s.headscale, "test_output")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Could not save log: %s\n", err)
|
log.Printf("Could not save log: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := pool.Purge(&headscale); err != nil {
|
if err := s.pool.Purge(&s.headscale); err != nil {
|
||||||
log.Printf("Could not purge resource: %s\n", err)
|
log.Printf("Could not purge resource: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := network.Close(); err != nil {
|
if err := s.network.Close(); err != nil {
|
||||||
log.Printf("Could not close network: %s\n", err)
|
log.Printf("Could not close network: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeCommand(resource *dockertest.Resource, cmd []string, env []string) (string, error) {
|
func (s *IntegrationTestSuite) saveLog(
|
||||||
var stdout bytes.Buffer
|
resource *dockertest.Resource,
|
||||||
var stderr bytes.Buffer
|
basePath string,
|
||||||
|
) error {
|
||||||
exitCode, err := resource.Exec(
|
|
||||||
cmd,
|
|
||||||
dockertest.ExecOptions{
|
|
||||||
Env: env,
|
|
||||||
StdOut: &stdout,
|
|
||||||
StdErr: &stderr,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if exitCode != 0 {
|
|
||||||
fmt.Println("Command: ", cmd)
|
|
||||||
fmt.Println("stdout: ", stdout.String())
|
|
||||||
fmt.Println("stderr: ", stderr.String())
|
|
||||||
return "", fmt.Errorf("command failed with: %s", stderr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return stdout.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveLog(resource *dockertest.Resource, basePath string) error {
|
|
||||||
err := os.MkdirAll(basePath, os.ModePerm)
|
err := os.MkdirAll(basePath, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -130,7 +100,7 @@ func saveLog(resource *dockertest.Resource, basePath string) error {
|
|||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
err = pool.Client.Logs(
|
err = s.pool.Client.Logs(
|
||||||
docker.LogsOptions{
|
docker.LogsOptions{
|
||||||
Context: context.TODO(),
|
Context: context.TODO(),
|
||||||
Container: resource.Container.ID,
|
Container: resource.Container.ID,
|
||||||
@ -150,12 +120,20 @@ func saveLog(resource *dockertest.Resource, basePath string) error {
|
|||||||
|
|
||||||
fmt.Printf("Saving logs for %s to %s\n", resource.Container.Name, basePath)
|
fmt.Printf("Saving logs for %s to %s\n", resource.Container.Name, basePath)
|
||||||
|
|
||||||
err = ioutil.WriteFile(path.Join(basePath, resource.Container.Name+".stdout.log"), []byte(stdout.String()), 0o644)
|
err = ioutil.WriteFile(
|
||||||
|
path.Join(basePath, resource.Container.Name+".stdout.log"),
|
||||||
|
[]byte(stdout.String()),
|
||||||
|
0o644,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(path.Join(basePath, resource.Container.Name+".stderr.log"), []byte(stdout.String()), 0o644)
|
err = ioutil.WriteFile(
|
||||||
|
path.Join(basePath, resource.Container.Name+".stderr.log"),
|
||||||
|
[]byte(stdout.String()),
|
||||||
|
0o644,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -163,15 +141,9 @@ func saveLog(resource *dockertest.Resource, basePath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dockerRestartPolicy(config *docker.HostConfig) {
|
func (s *IntegrationTestSuite) tailscaleContainer(
|
||||||
// set AutoRemove to true so that stopped container goes away by itself
|
namespace, identifier, version string,
|
||||||
config.AutoRemove = true
|
) (string, *dockertest.Resource) {
|
||||||
config.RestartPolicy = docker.RestartPolicy{
|
|
||||||
Name: "no",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tailscaleContainer(namespace, identifier, version string) (string, *dockertest.Resource) {
|
|
||||||
tailscaleBuildOptions := &dockertest.BuildOptions{
|
tailscaleBuildOptions := &dockertest.BuildOptions{
|
||||||
Dockerfile: "Dockerfile.tailscale",
|
Dockerfile: "Dockerfile.tailscale",
|
||||||
ContextDir: ".",
|
ContextDir: ".",
|
||||||
@ -182,36 +154,50 @@ func tailscaleContainer(namespace, identifier, version string) (string, *dockert
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
hostname := fmt.Sprintf("%s-tailscale-%s-%s", namespace, strings.Replace(version, ".", "-", -1), identifier)
|
hostname := fmt.Sprintf(
|
||||||
|
"%s-tailscale-%s-%s",
|
||||||
|
namespace,
|
||||||
|
strings.Replace(version, ".", "-", -1),
|
||||||
|
identifier,
|
||||||
|
)
|
||||||
tailscaleOptions := &dockertest.RunOptions{
|
tailscaleOptions := &dockertest.RunOptions{
|
||||||
Name: hostname,
|
Name: hostname,
|
||||||
Networks: []*dockertest.Network{&network},
|
Networks: []*dockertest.Network{&s.network},
|
||||||
Cmd: []string{"tailscaled", "--tun=userspace-networking", "--socks5-server=localhost:1055"},
|
Cmd: []string{
|
||||||
|
"tailscaled",
|
||||||
|
"--tun=userspace-networking",
|
||||||
|
"--socks5-server=localhost:1055",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pts, err := pool.BuildAndRunWithBuildOptions(tailscaleBuildOptions, tailscaleOptions, dockerRestartPolicy)
|
pts, err := s.pool.BuildAndRunWithBuildOptions(
|
||||||
|
tailscaleBuildOptions,
|
||||||
|
tailscaleOptions,
|
||||||
|
DockerRestartPolicy,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Could not start resource: %s", err)
|
log.Fatalf("Could not start resource: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Created %s container\n", hostname)
|
fmt.Printf("Created %s container\n", hostname)
|
||||||
|
|
||||||
return hostname, pts
|
return hostname, pts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) SetupSuite() {
|
func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
var err error
|
var err error
|
||||||
h = Headscale{
|
app = Headscale{
|
||||||
dbType: "sqlite3",
|
dbType: "sqlite3",
|
||||||
dbString: "integration_test_db.sqlite3",
|
dbString: "integration_test_db.sqlite3",
|
||||||
}
|
}
|
||||||
|
|
||||||
if ppool, err := dockertest.NewPool(""); err == nil {
|
if ppool, err := dockertest.NewPool(""); err == nil {
|
||||||
pool = *ppool
|
s.pool = *ppool
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("Could not connect to docker: %s", err)
|
log.Fatalf("Could not connect to docker: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if pnetwork, err := pool.CreateNetwork("headscale-test"); err == nil {
|
if pnetwork, err := s.pool.CreateNetwork("headscale-test"); err == nil {
|
||||||
network = *pnetwork
|
s.network = *pnetwork
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("Could not create network: %s", err)
|
log.Fatalf("Could not create network: %s", err)
|
||||||
}
|
}
|
||||||
@ -231,13 +217,13 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
|||||||
Mounts: []string{
|
Mounts: []string{
|
||||||
fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath),
|
fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath),
|
||||||
},
|
},
|
||||||
Networks: []*dockertest.Network{&network},
|
Networks: []*dockertest.Network{&s.network},
|
||||||
Cmd: []string{"headscale", "serve"},
|
Cmd: []string{"headscale", "serve"},
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Creating headscale container")
|
fmt.Println("Creating headscale container")
|
||||||
if pheadscale, err := pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, dockerRestartPolicy); err == nil {
|
if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil {
|
||||||
headscale = *pheadscale
|
s.headscale = *pheadscale
|
||||||
} else {
|
} else {
|
||||||
log.Fatalf("Could not start resource: %s", err)
|
log.Fatalf("Could not start resource: %s", err)
|
||||||
}
|
}
|
||||||
@ -248,23 +234,30 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
|||||||
for i := 0; i < scales.count; i++ {
|
for i := 0; i < scales.count; i++ {
|
||||||
version := tailscaleVersions[i%len(tailscaleVersions)]
|
version := tailscaleVersions[i%len(tailscaleVersions)]
|
||||||
|
|
||||||
hostname, container := tailscaleContainer(namespace, fmt.Sprint(i), version)
|
hostname, container := s.tailscaleContainer(
|
||||||
|
namespace,
|
||||||
|
fmt.Sprint(i),
|
||||||
|
version,
|
||||||
|
)
|
||||||
scales.tailscales[hostname] = *container
|
scales.tailscales[hostname] = *container
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Waiting for headscale to be ready")
|
fmt.Println("Waiting for headscale to be ready")
|
||||||
hostEndpoint := fmt.Sprintf("localhost:%s", headscale.GetPort("8080/tcp"))
|
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8080/tcp"))
|
||||||
|
|
||||||
if err := pool.Retry(func() error {
|
if err := s.pool.Retry(func() error {
|
||||||
url := fmt.Sprintf("http://%s/health", hostEndpoint)
|
url := fmt.Sprintf("http://%s/health", hostEndpoint)
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("status code not OK")
|
return fmt.Errorf("status code not OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
// TODO(kradalby): If we cannot access headscale, or any other fatal error during
|
// TODO(kradalby): If we cannot access headscale, or any other fatal error during
|
||||||
@ -277,17 +270,17 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
|||||||
|
|
||||||
for namespace, scales := range s.namespaces {
|
for namespace, scales := range s.namespaces {
|
||||||
fmt.Printf("Creating headscale namespace: %s\n", namespace)
|
fmt.Printf("Creating headscale namespace: %s\n", namespace)
|
||||||
result, err := executeCommand(
|
result, err := ExecuteCommand(
|
||||||
&headscale,
|
&s.headscale,
|
||||||
[]string{"headscale", "namespaces", "create", namespace},
|
[]string{"headscale", "namespaces", "create", namespace},
|
||||||
[]string{},
|
[]string{},
|
||||||
)
|
)
|
||||||
assert.Nil(s.T(), err)
|
|
||||||
fmt.Println("headscale create namespace result: ", result)
|
fmt.Println("headscale create namespace result: ", result)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
fmt.Printf("Creating pre auth key for %s\n", namespace)
|
fmt.Printf("Creating pre auth key for %s\n", namespace)
|
||||||
authKey, err := executeCommand(
|
preAuthResult, err := ExecuteCommand(
|
||||||
&headscale,
|
&s.headscale,
|
||||||
[]string{
|
[]string{
|
||||||
"headscale",
|
"headscale",
|
||||||
"--namespace",
|
"--namespace",
|
||||||
@ -297,14 +290,24 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
|||||||
"--reusable",
|
"--reusable",
|
||||||
"--expiration",
|
"--expiration",
|
||||||
"24h",
|
"24h",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
},
|
},
|
||||||
[]string{},
|
[]string{"LOG_LEVEL=error"},
|
||||||
)
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
|
var preAuthKey v1.PreAuthKey
|
||||||
|
err = json.Unmarshal([]byte(preAuthResult), &preAuthKey)
|
||||||
|
assert.Nil(s.T(), err)
|
||||||
|
assert.True(s.T(), preAuthKey.Reusable)
|
||||||
|
|
||||||
headscaleEndpoint := "http://headscale:8080"
|
headscaleEndpoint := "http://headscale:8080"
|
||||||
|
|
||||||
fmt.Printf("Joining tailscale containers to headscale at %s\n", headscaleEndpoint)
|
fmt.Printf(
|
||||||
|
"Joining tailscale containers to headscale at %s\n",
|
||||||
|
headscaleEndpoint,
|
||||||
|
)
|
||||||
for hostname, tailscale := range scales.tailscales {
|
for hostname, tailscale := range scales.tailscales {
|
||||||
command := []string{
|
command := []string{
|
||||||
"tailscale",
|
"tailscale",
|
||||||
@ -312,14 +315,14 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
|||||||
"-login-server",
|
"-login-server",
|
||||||
headscaleEndpoint,
|
headscaleEndpoint,
|
||||||
"--authkey",
|
"--authkey",
|
||||||
strings.TrimSuffix(authKey, "\n"),
|
preAuthKey.Key,
|
||||||
"--hostname",
|
"--hostname",
|
||||||
hostname,
|
hostname,
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Join command:", command)
|
fmt.Println("Join command:", command)
|
||||||
fmt.Printf("Running join command for %s\n", hostname)
|
fmt.Printf("Running join command for %s\n", hostname)
|
||||||
result, err := executeCommand(
|
result, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
[]string{},
|
[]string{},
|
||||||
@ -338,15 +341,18 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
|||||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
func (s *IntegrationTestSuite) TearDownSuite() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) HandleStats(suiteName string, stats *suite.SuiteInformation) {
|
func (s *IntegrationTestSuite) HandleStats(
|
||||||
|
suiteName string,
|
||||||
|
stats *suite.SuiteInformation,
|
||||||
|
) {
|
||||||
s.stats = stats
|
s.stats = stats
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestListNodes() {
|
func (s *IntegrationTestSuite) TestListNodes() {
|
||||||
for namespace, scales := range s.namespaces {
|
for namespace, scales := range s.namespaces {
|
||||||
fmt.Println("Listing nodes")
|
fmt.Println("Listing nodes")
|
||||||
result, err := executeCommand(
|
result, err := ExecuteCommand(
|
||||||
&headscale,
|
&s.headscale,
|
||||||
[]string{"headscale", "--namespace", namespace, "nodes", "list"},
|
[]string{"headscale", "--namespace", namespace, "nodes", "list"},
|
||||||
[]string{},
|
[]string{},
|
||||||
)
|
)
|
||||||
@ -387,46 +393,50 @@ func (s *IntegrationTestSuite) TestGetIpAddresses() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestSuite) TestStatus() {
|
// TODO(kradalby): fix this test
|
||||||
for _, scales := range s.namespaces {
|
// We need some way to impot ipnstate.Status from multiple go packages.
|
||||||
ips, err := getIPs(scales.tailscales)
|
// Currently it will only work with 1.18.x since that is the last
|
||||||
assert.Nil(s.T(), err)
|
// version we have in go.mod
|
||||||
|
// func (s *IntegrationTestSuite) TestStatus() {
|
||||||
for hostname, tailscale := range scales.tailscales {
|
// for _, scales := range s.namespaces {
|
||||||
s.T().Run(hostname, func(t *testing.T) {
|
// ips, err := getIPs(scales.tailscales)
|
||||||
command := []string{"tailscale", "status", "--json"}
|
// assert.Nil(s.T(), err)
|
||||||
|
//
|
||||||
fmt.Printf("Getting status for %s\n", hostname)
|
// for hostname, tailscale := range scales.tailscales {
|
||||||
result, err := executeCommand(
|
// s.T().Run(hostname, func(t *testing.T) {
|
||||||
&tailscale,
|
// command := []string{"tailscale", "status", "--json"}
|
||||||
command,
|
//
|
||||||
[]string{},
|
// fmt.Printf("Getting status for %s\n", hostname)
|
||||||
)
|
// result, err := ExecuteCommand(
|
||||||
assert.Nil(t, err)
|
// &tailscale,
|
||||||
|
// command,
|
||||||
var status ipnstate.Status
|
// []string{},
|
||||||
err = json.Unmarshal([]byte(result), &status)
|
// )
|
||||||
assert.Nil(s.T(), err)
|
// assert.Nil(t, err)
|
||||||
|
//
|
||||||
// TODO(kradalby): Replace this check with peer length of SAME namespace
|
// var status ipnstate.Status
|
||||||
// Check if we have as many nodes in status
|
// err = json.Unmarshal([]byte(result), &status)
|
||||||
// as we have IPs/tailscales
|
// assert.Nil(s.T(), err)
|
||||||
// lines := strings.Split(result, "\n")
|
//
|
||||||
// assert.Equal(t, len(ips), len(lines)-1)
|
// // TODO(kradalby): Replace this check with peer length of SAME namespace
|
||||||
// assert.Equal(t, len(scales.tailscales), len(lines)-1)
|
// // Check if we have as many nodes in status
|
||||||
|
// // as we have IPs/tailscales
|
||||||
peerIps := getIPsfromIPNstate(status)
|
// // lines := strings.Split(result, "\n")
|
||||||
|
// // assert.Equal(t, len(ips), len(lines)-1)
|
||||||
// Check that all hosts is present in all hosts status
|
// // assert.Equal(t, len(scales.tailscales), len(lines)-1)
|
||||||
for ipHostname, ip := range ips {
|
//
|
||||||
if hostname != ipHostname {
|
// peerIps := getIPsfromIPNstate(status)
|
||||||
assert.Contains(t, peerIps, ip)
|
//
|
||||||
}
|
// // Check that all hosts is present in all hosts status
|
||||||
}
|
// for ipHostname, ip := range ips {
|
||||||
})
|
// if hostname != ipHostname {
|
||||||
}
|
// assert.Contains(t, peerIps, ip)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
|
func getIPsfromIPNstate(status ipnstate.Status) []netaddr.IP {
|
||||||
ips := make([]netaddr.IP, 0)
|
ips := make([]netaddr.IP, 0)
|
||||||
@ -458,8 +468,14 @@ func (s *IntegrationTestSuite) TestPingAllPeers() {
|
|||||||
ip.String(),
|
ip.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Pinging from %s (%s) to %s (%s)\n", hostname, ips[hostname], peername, ip)
|
fmt.Printf(
|
||||||
result, err := executeCommand(
|
"Pinging from %s (%s) to %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
ips[hostname],
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
|
result, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
[]string{},
|
[]string{},
|
||||||
@ -478,22 +494,35 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
|
|||||||
main := s.namespaces["main"]
|
main := s.namespaces["main"]
|
||||||
shared := s.namespaces["shared"]
|
shared := s.namespaces["shared"]
|
||||||
|
|
||||||
result, err := executeCommand(
|
result, err := ExecuteCommand(
|
||||||
&headscale,
|
&s.headscale,
|
||||||
[]string{"headscale", "nodes", "list", "-o", "json", "--namespace", "shared"},
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"nodes",
|
||||||
|
"list",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
"--namespace",
|
||||||
|
"shared",
|
||||||
|
},
|
||||||
[]string{},
|
[]string{},
|
||||||
)
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
var machineList []Machine
|
var machineList []v1.Machine
|
||||||
err = json.Unmarshal([]byte(result), &machineList)
|
err = json.Unmarshal([]byte(result), &machineList)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
|
|
||||||
for _, machine := range machineList {
|
for _, machine := range machineList {
|
||||||
|
result, err := ExecuteCommand(
|
||||||
result, err := executeCommand(
|
&s.headscale,
|
||||||
&headscale,
|
[]string{
|
||||||
[]string{"headscale", "nodes", "share", "--namespace", "shared", fmt.Sprint(machine.ID), "main"},
|
"headscale",
|
||||||
|
"nodes",
|
||||||
|
"share",
|
||||||
|
"--identifier", fmt.Sprint(machine.Id),
|
||||||
|
"--namespace", "main",
|
||||||
|
},
|
||||||
[]string{},
|
[]string{},
|
||||||
)
|
)
|
||||||
assert.Nil(s.T(), err)
|
assert.Nil(s.T(), err)
|
||||||
@ -501,8 +530,8 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
|
|||||||
fmt.Println("Shared node with result: ", result)
|
fmt.Println("Shared node with result: ", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err = executeCommand(
|
result, err = ExecuteCommand(
|
||||||
&headscale,
|
&s.headscale,
|
||||||
[]string{"headscale", "nodes", "list", "--namespace", "main"},
|
[]string{"headscale", "nodes", "list", "--namespace", "main"},
|
||||||
[]string{},
|
[]string{},
|
||||||
)
|
)
|
||||||
@ -545,8 +574,14 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
|
|||||||
ip.String(),
|
ip.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Pinging from %s (%s) to %s (%s)\n", hostname, mainIps[hostname], peername, ip)
|
fmt.Printf(
|
||||||
result, err := executeCommand(
|
"Pinging from %s (%s) to %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
mainIps[hostname],
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
|
result, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
[]string{},
|
[]string{},
|
||||||
@ -569,7 +604,7 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
|||||||
|
|
||||||
for hostname, tailscale := range scales.tailscales {
|
for hostname, tailscale := range scales.tailscales {
|
||||||
command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", hostname)}
|
command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", hostname)}
|
||||||
_, err := executeCommand(
|
_, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
[]string{},
|
[]string{},
|
||||||
@ -578,7 +613,6 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
|||||||
for peername, ip := range ips {
|
for peername, ip := range ips {
|
||||||
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
s.T().Run(fmt.Sprintf("%s-%s", hostname, peername), func(t *testing.T) {
|
||||||
if peername != hostname {
|
if peername != hostname {
|
||||||
|
|
||||||
// Under normal circumstances, we should be able to send a file
|
// Under normal circumstances, we should be able to send a file
|
||||||
// using `tailscale file cp` - but not in userspace networking mode
|
// using `tailscale file cp` - but not in userspace networking mode
|
||||||
// So curl!
|
// So curl!
|
||||||
@ -603,10 +637,20 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
|||||||
"PUT",
|
"PUT",
|
||||||
"--upload-file",
|
"--upload-file",
|
||||||
fmt.Sprintf("/tmp/file_from_%s", hostname),
|
fmt.Sprintf("/tmp/file_from_%s", hostname),
|
||||||
fmt.Sprintf("%s/v0/put/file_from_%s", peerAPI, hostname),
|
fmt.Sprintf(
|
||||||
|
"%s/v0/put/file_from_%s",
|
||||||
|
peerAPI,
|
||||||
|
hostname,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
fmt.Printf("Sending file from %s (%s) to %s (%s)\n", hostname, ips[hostname], peername, ip)
|
fmt.Printf(
|
||||||
_, err = executeCommand(
|
"Sending file from %s (%s) to %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
ips[hostname],
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
|
_, err = ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
[]string{"ALL_PROXY=socks5://localhost:1055"},
|
[]string{"ALL_PROXY=socks5://localhost:1055"},
|
||||||
@ -633,7 +677,7 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
|||||||
"get",
|
"get",
|
||||||
"/tmp/",
|
"/tmp/",
|
||||||
}
|
}
|
||||||
_, err := executeCommand(
|
_, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
[]string{},
|
[]string{},
|
||||||
@ -646,15 +690,25 @@ func (s *IntegrationTestSuite) TestTailDrop() {
|
|||||||
"ls",
|
"ls",
|
||||||
fmt.Sprintf("/tmp/file_from_%s", peername),
|
fmt.Sprintf("/tmp/file_from_%s", peername),
|
||||||
}
|
}
|
||||||
fmt.Printf("Checking file in %s (%s) from %s (%s)\n", hostname, ips[hostname], peername, ip)
|
fmt.Printf(
|
||||||
result, err := executeCommand(
|
"Checking file in %s (%s) from %s (%s)\n",
|
||||||
|
hostname,
|
||||||
|
ips[hostname],
|
||||||
|
peername,
|
||||||
|
ip,
|
||||||
|
)
|
||||||
|
result, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
[]string{},
|
[]string{},
|
||||||
)
|
)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
fmt.Printf("Result for %s: %s\n", peername, result)
|
fmt.Printf("Result for %s: %s\n", peername, result)
|
||||||
assert.Equal(t, result, fmt.Sprintf("/tmp/file_from_%s\n", peername))
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
result,
|
||||||
|
fmt.Sprintf("/tmp/file_from_%s\n", peername),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -685,7 +739,7 @@ func (s *IntegrationTestSuite) TestMagicDNS() {
|
|||||||
peername,
|
peername,
|
||||||
ip,
|
ip,
|
||||||
)
|
)
|
||||||
result, err := executeCommand(
|
result, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
[]string{},
|
[]string{},
|
||||||
@ -705,7 +759,7 @@ func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, e
|
|||||||
for hostname, tailscale := range tailscales {
|
for hostname, tailscale := range tailscales {
|
||||||
command := []string{"tailscale", "ip"}
|
command := []string{"tailscale", "ip"}
|
||||||
|
|
||||||
result, err := executeCommand(
|
result, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
[]string{},
|
[]string{},
|
||||||
@ -721,10 +775,13 @@ func getIPs(tailscales map[string]dockertest.Resource) (map[string]netaddr.IP, e
|
|||||||
|
|
||||||
ips[hostname] = ip
|
ips[hostname] = ip
|
||||||
}
|
}
|
||||||
|
|
||||||
return ips, nil
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAPIURLs(tailscales map[string]dockertest.Resource) (map[netaddr.IP]string, error) {
|
func getAPIURLs(
|
||||||
|
tailscales map[string]dockertest.Resource,
|
||||||
|
) (map[netaddr.IP]string, error) {
|
||||||
fts := make(map[netaddr.IP]string)
|
fts := make(map[netaddr.IP]string)
|
||||||
for _, tailscale := range tailscales {
|
for _, tailscale := range tailscales {
|
||||||
command := []string{
|
command := []string{
|
||||||
@ -733,7 +790,7 @@ func getAPIURLs(tailscales map[string]dockertest.Resource) (map[netaddr.IP]strin
|
|||||||
"/run/tailscale/tailscaled.sock",
|
"/run/tailscale/tailscaled.sock",
|
||||||
"http://localhost/localapi/v0/file-targets",
|
"http://localhost/localapi/v0/file-targets",
|
||||||
}
|
}
|
||||||
result, err := executeCommand(
|
result, err := ExecuteCommand(
|
||||||
&tailscale,
|
&tailscale,
|
||||||
command,
|
command,
|
||||||
[]string{},
|
[]string{},
|
||||||
@ -758,5 +815,6 @@ func getAPIURLs(tailscales map[string]dockertest.Resource) (map[netaddr.IP]strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fts, nil
|
return fts, nil
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
SEmQwCu+tGywQWEUsf93TpTRUvlB7WhnCdHgWrSXjEA=
|
|
@ -1,18 +0,0 @@
|
|||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: headscale
|
|
||||||
annotations:
|
|
||||||
kubernetes.io/ingress.class: traefik
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: $(PUBLIC_HOSTNAME)
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- backend:
|
|
||||||
service:
|
|
||||||
name: headscale
|
|
||||||
port:
|
|
||||||
number: 8080
|
|
||||||
path: /
|
|
||||||
pathType: Prefix
|
|
@ -1,42 +0,0 @@
|
|||||||
namespace: headscale
|
|
||||||
resources:
|
|
||||||
- configmap.yaml
|
|
||||||
- ingress.yaml
|
|
||||||
- service.yaml
|
|
||||||
generatorOptions:
|
|
||||||
disableNameSuffixHash: true
|
|
||||||
configMapGenerator:
|
|
||||||
- name: headscale-site
|
|
||||||
files:
|
|
||||||
- derp.yaml=site/derp.yaml
|
|
||||||
envs:
|
|
||||||
- site/public.env
|
|
||||||
- name: headscale-etc
|
|
||||||
literals:
|
|
||||||
- config.json={}
|
|
||||||
secretGenerator:
|
|
||||||
- name: headscale
|
|
||||||
files:
|
|
||||||
- secrets/private-key
|
|
||||||
vars:
|
|
||||||
- name: PUBLIC_PROTO
|
|
||||||
objRef:
|
|
||||||
kind: ConfigMap
|
|
||||||
name: headscale-site
|
|
||||||
apiVersion: v1
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: data.public-proto
|
|
||||||
- name: PUBLIC_HOSTNAME
|
|
||||||
objRef:
|
|
||||||
kind: ConfigMap
|
|
||||||
name: headscale-site
|
|
||||||
apiVersion: v1
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: data.public-hostname
|
|
||||||
- name: CONTACT_EMAIL
|
|
||||||
objRef:
|
|
||||||
kind: ConfigMap
|
|
||||||
name: headscale-site
|
|
||||||
apiVersion: v1
|
|
||||||
fieldRef:
|
|
||||||
fieldPath: data.contact-email
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user