mirror of
https://github.com/juanfont/headscale.git
synced 2024-11-30 02:43:05 +00:00
Merge branch 'main' into typofix
This commit is contained in:
commit
e93529e9f3
17 changed files with 117 additions and 91 deletions
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -70,7 +70,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: bufbuild/buf-setup-action@v0.7.0
|
- uses: bufbuild/buf-setup-action@v1.7.0
|
||||||
- uses: bufbuild/buf-lint-action@v1
|
- uses: bufbuild/buf-lint-action@v1
|
||||||
with:
|
with:
|
||||||
input: "proto"
|
input: "proto"
|
||||||
|
|
27
.github/workflows/test-integration.yml
vendored
27
.github/workflows/test-integration.yml
vendored
|
@ -11,6 +11,11 @@ jobs:
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Set Swap Space
|
||||||
|
uses: pierotofy/set-swap-space@master
|
||||||
|
with:
|
||||||
|
swap-size-gb: 10
|
||||||
|
|
||||||
- name: Get changed files
|
- name: Get changed files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: tj-actions/changed-files@v14.1
|
uses: tj-actions/changed-files@v14.1
|
||||||
|
@ -25,11 +30,29 @@ jobs:
|
||||||
- uses: cachix/install-nix-action@v16
|
- uses: cachix/install-nix-action@v16
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
|
||||||
- name: Run Integration tests
|
- name: Run CLI integration tests
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
uses: nick-fields/retry@v2
|
uses: nick-fields/retry@v2
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 240
|
timeout_minutes: 240
|
||||||
max_attempts: 5
|
max_attempts: 5
|
||||||
retry_on: error
|
retry_on: error
|
||||||
command: nix develop --command -- make test_integration
|
command: nix develop --command -- make test_integration_cli
|
||||||
|
|
||||||
|
- name: Run Embedded DERP server integration tests
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
uses: nick-fields/retry@v2
|
||||||
|
with:
|
||||||
|
timeout_minutes: 240
|
||||||
|
max_attempts: 5
|
||||||
|
retry_on: error
|
||||||
|
command: nix develop --command -- make test_integration_derp
|
||||||
|
|
||||||
|
- name: Run general integration tests
|
||||||
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
|
uses: nick-fields/retry@v2
|
||||||
|
with:
|
||||||
|
timeout_minutes: 240
|
||||||
|
max_attempts: 5
|
||||||
|
retry_on: error
|
||||||
|
command: nix develop --command -- make test_integration_general
|
||||||
|
|
10
Makefile
10
Makefile
|
@ -24,14 +24,16 @@ dev: lint test build
|
||||||
test:
|
test:
|
||||||
@go test -coverprofile=coverage.out ./...
|
@go test -coverprofile=coverage.out ./...
|
||||||
|
|
||||||
test_integration:
|
test_integration: test_integration_cli test_integration_derp test_integration_general
|
||||||
go test -failfast -tags integration -timeout 30m -count=1 ./...
|
|
||||||
|
|
||||||
test_integration_cli:
|
test_integration_cli:
|
||||||
go test -tags integration -v integration_cli_test.go integration_common_test.go
|
go test -failfast -tags integration_cli,integration -timeout 30m -count=1 ./...
|
||||||
|
|
||||||
test_integration_derp:
|
test_integration_derp:
|
||||||
go test -tags integration -v integration_embedded_derp_test.go integration_common_test.go
|
go test -failfast -tags integration_derp,integration -timeout 30m -count=1 ./...
|
||||||
|
|
||||||
|
test_integration_general:
|
||||||
|
go test -failfast -tags integration_general,integration -timeout 30m -count=1 ./...
|
||||||
|
|
||||||
coverprofile_func:
|
coverprofile_func:
|
||||||
go tool cover -func=coverage.out
|
go tool cover -func=coverage.out
|
||||||
|
|
|
@ -14,7 +14,7 @@ const (
|
||||||
apiPrefixLength = 7
|
apiPrefixLength = 7
|
||||||
apiKeyLength = 32
|
apiKeyLength = 32
|
||||||
|
|
||||||
errAPIKeyFailedToParse = Error("Failed to parse ApiKey")
|
ErrAPIKeyFailedToParse = Error("Failed to parse ApiKey")
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIKey describes the datamodel for API keys used to remotely authenticate with
|
// APIKey describes the datamodel for API keys used to remotely authenticate with
|
||||||
|
@ -116,7 +116,7 @@ func (h *Headscale) ExpireAPIKey(key *APIKey) error {
|
||||||
func (h *Headscale) ValidateAPIKey(keyStr string) (bool, error) {
|
func (h *Headscale) ValidateAPIKey(keyStr string) (bool, error) {
|
||||||
prefix, hash, found := strings.Cut(keyStr, ".")
|
prefix, hash, found := strings.Cut(keyStr, ".")
|
||||||
if !found {
|
if !found {
|
||||||
return false, errAPIKeyFailedToParse
|
return false, ErrAPIKeyFailedToParse
|
||||||
}
|
}
|
||||||
|
|
||||||
key, err := h.GetAPIKey(prefix)
|
key, err := h.GetAPIKey(prefix)
|
||||||
|
|
6
db.go
6
db.go
|
@ -248,7 +248,7 @@ func (hi *HostInfo) Scan(destination interface{}) error {
|
||||||
return json.Unmarshal([]byte(value), hi)
|
return json.Unmarshal([]byte(value), hi)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
|
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +270,7 @@ func (i *IPPrefixes) Scan(destination interface{}) error {
|
||||||
return json.Unmarshal([]byte(value), i)
|
return json.Unmarshal([]byte(value), i)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
|
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ func (i *StringList) Scan(destination interface{}) error {
|
||||||
return json.Unmarshal([]byte(value), i)
|
return json.Unmarshal([]byte(value), i)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
|
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build integration
|
//go:build integration_cli
|
||||||
// +build integration
|
|
||||||
|
|
||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
|
@ -73,21 +72,22 @@ func (s *IntegrationCLITestSuite) SetupTest() {
|
||||||
s.FailNow(fmt.Sprintf("Could not remove existing container before building test: %s", err), "")
|
s.FailNow(fmt.Sprintf("Could not remove existing container before building test: %s", err), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Creating headscale container")
|
fmt.Println("Creating headscale container for CLI tests")
|
||||||
if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil {
|
if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil {
|
||||||
s.headscale = *pheadscale
|
s.headscale = *pheadscale
|
||||||
} else {
|
} else {
|
||||||
s.FailNow(fmt.Sprintf("Could not start headscale container: %s", err), "")
|
s.FailNow(fmt.Sprintf("Could not start headscale container: %s", err), "")
|
||||||
}
|
}
|
||||||
fmt.Println("Created headscale container")
|
fmt.Println("Created headscale container for CLI tests")
|
||||||
|
|
||||||
fmt.Println("Waiting for headscale to be ready")
|
fmt.Println("Waiting for headscale to be ready for CLI tests")
|
||||||
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8080/tcp"))
|
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8080/tcp"))
|
||||||
|
|
||||||
if err := s.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 {
|
||||||
|
fmt.Printf("headscale for CLI test is not ready: %s\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
@ -102,7 +102,7 @@ func (s *IntegrationCLITestSuite) SetupTest() {
|
||||||
// https://github.com/stretchr/testify/issues/849
|
// https://github.com/stretchr/testify/issues/849
|
||||||
return // fmt.Errorf("Could not connect to headscale: %s", err)
|
return // fmt.Errorf("Could not connect to headscale: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println("headscale container is ready")
|
fmt.Println("headscale container is ready for CLI tests")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationCLITestSuite) TearDownTest() {
|
func (s *IntegrationCLITestSuite) TearDownTest() {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build integration
|
//go:build integration
|
||||||
// +build integration
|
|
||||||
|
|
||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
|
@ -20,6 +19,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
headscaleHostname = "headscale-derp"
|
||||||
DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
|
DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,9 +30,10 @@ var (
|
||||||
IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48")
|
IpPrefix6 = netaddr.MustParseIPPrefix("fd7a:115c:a1e0::/48")
|
||||||
|
|
||||||
tailscaleVersions = []string{
|
tailscaleVersions = []string{
|
||||||
"head",
|
// "head",
|
||||||
"unstable",
|
// "unstable",
|
||||||
"1.26.0",
|
"1.28.0",
|
||||||
|
"1.26.2",
|
||||||
"1.24.2",
|
"1.24.2",
|
||||||
"1.22.2",
|
"1.22.2",
|
||||||
"1.20.4",
|
"1.20.4",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build integration
|
//go:build integration_derp
|
||||||
|
|
||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
|
@ -28,9 +28,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
headscaleHostname = "headscale-derp"
|
namespaceName = "derpnamespace"
|
||||||
namespaceName = "derpnamespace"
|
totalContainers = 3
|
||||||
totalContainers = 3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntegrationDERPTestSuite struct {
|
type IntegrationDERPTestSuite struct {
|
||||||
|
@ -134,15 +133,15 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
|
||||||
s.FailNow(fmt.Sprintf("Could not remove existing container before building test: %s", err), "")
|
s.FailNow(fmt.Sprintf("Could not remove existing container before building test: %s", err), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Creating headscale container")
|
log.Println("Creating headscale container for DERP integration tests")
|
||||||
if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil {
|
if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil {
|
||||||
s.headscale = *pheadscale
|
s.headscale = *pheadscale
|
||||||
} else {
|
} else {
|
||||||
s.FailNow(fmt.Sprintf("Could not start headscale container: %s", err), "")
|
s.FailNow(fmt.Sprintf("Could not start headscale container: %s", err), "")
|
||||||
}
|
}
|
||||||
log.Println("Created headscale container to test DERP")
|
log.Println("Created headscale container for embedded DERP tests")
|
||||||
|
|
||||||
log.Println("Creating tailscale containers")
|
log.Println("Creating tailscale containers for embedded DERP tests")
|
||||||
|
|
||||||
for i := 0; i < totalContainers; i++ {
|
for i := 0; i < totalContainers; i++ {
|
||||||
version := tailscaleVersions[i%len(tailscaleVersions)]
|
version := tailscaleVersions[i%len(tailscaleVersions)]
|
||||||
|
@ -154,7 +153,7 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
|
||||||
s.tailscales[hostname] = *container
|
s.tailscales[hostname] = *container
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Waiting for headscale to be ready")
|
log.Println("Waiting for headscale to be ready for embedded DERP tests")
|
||||||
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8443/tcp"))
|
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8443/tcp"))
|
||||||
|
|
||||||
if err := s.pool.Retry(func() error {
|
if err := s.pool.Retry(func() error {
|
||||||
|
@ -164,6 +163,7 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
|
||||||
client := &http.Client{Transport: insecureTransport}
|
client := &http.Client{Transport: insecureTransport}
|
||||||
resp, err := client.Get(url)
|
resp, err := client.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Printf("headscale for embedded DERP tests is not ready: %s\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
|
||||||
// https://github.com/stretchr/testify/issues/849
|
// https://github.com/stretchr/testify/issues/849
|
||||||
return // fmt.Errorf("Could not connect to headscale: %s", err)
|
return // fmt.Errorf("Could not connect to headscale: %s", err)
|
||||||
}
|
}
|
||||||
log.Println("headscale container is ready")
|
log.Println("headscale container is ready for embedded DERP tests")
|
||||||
|
|
||||||
log.Printf("Creating headscale namespace: %s\n", namespaceName)
|
log.Printf("Creating headscale namespace: %s\n", namespaceName)
|
||||||
result, err := ExecuteCommand(
|
result, err := ExecuteCommand(
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//go:build integration
|
//go:build integration_general
|
||||||
// +build integration
|
|
||||||
|
|
||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
|
@ -251,15 +250,15 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
s.FailNow(fmt.Sprintf("Could not remove existing container before building test: %s", err), "")
|
s.FailNow(fmt.Sprintf("Could not remove existing container before building test: %s", err), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Creating headscale container")
|
log.Println("Creating headscale container for core integration tests")
|
||||||
if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil {
|
if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil {
|
||||||
s.headscale = *pheadscale
|
s.headscale = *pheadscale
|
||||||
} else {
|
} else {
|
||||||
s.FailNow(fmt.Sprintf("Could not start headscale container: %s", err), "")
|
s.FailNow(fmt.Sprintf("Could not start headscale container for core integration tests: %s", err), "")
|
||||||
}
|
}
|
||||||
log.Println("Created headscale container")
|
log.Println("Created headscale container for core integration tests")
|
||||||
|
|
||||||
log.Println("Creating tailscale containers")
|
log.Println("Creating tailscale containers for core integration tests")
|
||||||
for namespace, scales := range s.namespaces {
|
for namespace, scales := range s.namespaces {
|
||||||
for i := 0; i < scales.count; i++ {
|
for i := 0; i < scales.count; i++ {
|
||||||
version := tailscaleVersions[i%len(tailscaleVersions)]
|
version := tailscaleVersions[i%len(tailscaleVersions)]
|
||||||
|
@ -273,7 +272,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Waiting for headscale to be ready")
|
log.Println("Waiting for headscale to be ready for core integration tests")
|
||||||
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8080/tcp"))
|
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8080/tcp"))
|
||||||
|
|
||||||
if err := s.pool.Retry(func() error {
|
if err := s.pool.Retry(func() error {
|
||||||
|
@ -281,6 +280,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
|
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Printf("headscale for core integration test is not ready: %s\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,7 +296,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||||
// https://github.com/stretchr/testify/issues/849
|
// https://github.com/stretchr/testify/issues/849
|
||||||
return // fmt.Errorf("Could not connect to headscale: %s", err)
|
return // fmt.Errorf("Could not connect to headscale: %s", err)
|
||||||
}
|
}
|
||||||
log.Println("headscale container is ready")
|
log.Println("headscale container is ready for core integration tests")
|
||||||
|
|
||||||
for namespace, scales := range s.namespaces {
|
for namespace, scales := range s.namespaces {
|
||||||
log.Printf("Creating headscale namespace: %s\n", namespace)
|
log.Printf("Creating headscale namespace: %s\n", namespace)
|
24
machine.go
24
machine.go
|
@ -18,14 +18,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errMachineNotFound = Error("machine not found")
|
ErrMachineNotFound = Error("machine not found")
|
||||||
errMachineRouteIsNotAvailable = Error("route is not available on machine")
|
ErrMachineRouteIsNotAvailable = Error("route is not available on machine")
|
||||||
errMachineAddressesInvalid = Error("failed to parse machine addresses")
|
ErrMachineAddressesInvalid = Error("failed to parse machine addresses")
|
||||||
errMachineNotFoundRegistrationCache = Error(
|
ErrMachineNotFoundRegistrationCache = Error(
|
||||||
"machine not found in registration cache",
|
"machine not found in registration cache",
|
||||||
)
|
)
|
||||||
errCouldNotConvertMachineInterface = Error("failed to convert machine interface")
|
ErrCouldNotConvertMachineInterface = Error("failed to convert machine interface")
|
||||||
errHostnameTooLong = Error("Hostname too long")
|
ErrHostnameTooLong = Error("Hostname too long")
|
||||||
MachineGivenNameHashLength = 8
|
MachineGivenNameHashLength = 8
|
||||||
MachineGivenNameTrimSize = 2
|
MachineGivenNameTrimSize = 2
|
||||||
)
|
)
|
||||||
|
@ -112,7 +112,7 @@ func (ma *MachineAddresses) Scan(destination interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: unexpected data type %T", errMachineAddressesInvalid, destination)
|
return fmt.Errorf("%w: unexpected data type %T", ErrMachineAddressesInvalid, destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,7 +337,7 @@ func (h *Headscale) GetMachine(namespace string, name string) (*Machine, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errMachineNotFound
|
return nil, ErrMachineNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMachineByID finds a Machine by ID and returns the Machine struct.
|
// GetMachineByID finds a Machine by ID and returns the Machine struct.
|
||||||
|
@ -635,7 +635,7 @@ func (machine Machine) toNode(
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"hostname %q is too long it cannot except 255 ASCII chars: %w",
|
"hostname %q is too long it cannot except 255 ASCII chars: %w",
|
||||||
hostname,
|
hostname,
|
||||||
errHostnameTooLong,
|
ErrHostnameTooLong,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -785,11 +785,11 @@ func (h *Headscale) RegisterMachineFromAuthCallback(
|
||||||
|
|
||||||
return machine, err
|
return machine, err
|
||||||
} else {
|
} else {
|
||||||
return nil, errCouldNotConvertMachineInterface
|
return nil, ErrCouldNotConvertMachineInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errMachineNotFoundRegistrationCache
|
return nil, ErrMachineNotFoundRegistrationCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
|
// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey.
|
||||||
|
@ -877,7 +877,7 @@ func (h *Headscale) EnableRoutes(machine *Machine, routeStrs ...string) error {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"route (%s) is not available on node %s: %w",
|
"route (%s) is not available on node %s: %w",
|
||||||
machine.Hostname,
|
machine.Hostname,
|
||||||
newRoute, errMachineRouteIsNotAvailable,
|
newRoute, ErrMachineRouteIsNotAvailable,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errNamespaceExists = Error("Namespace already exists")
|
ErrNamespaceExists = Error("Namespace already exists")
|
||||||
errNamespaceNotFound = Error("Namespace not found")
|
ErrNamespaceNotFound = Error("Namespace not found")
|
||||||
errNamespaceNotEmptyOfNodes = Error("Namespace not empty: node(s) found")
|
ErrNamespaceNotEmptyOfNodes = Error("Namespace not empty: node(s) found")
|
||||||
errInvalidNamespaceName = Error("Invalid namespace name")
|
ErrInvalidNamespaceName = Error("Invalid namespace name")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -47,7 +47,7 @@ func (h *Headscale) CreateNamespace(name string) (*Namespace, error) {
|
||||||
}
|
}
|
||||||
namespace := Namespace{}
|
namespace := Namespace{}
|
||||||
if err := h.db.Where("name = ?", name).First(&namespace).Error; err == nil {
|
if err := h.db.Where("name = ?", name).First(&namespace).Error; err == nil {
|
||||||
return nil, errNamespaceExists
|
return nil, ErrNamespaceExists
|
||||||
}
|
}
|
||||||
namespace.Name = name
|
namespace.Name = name
|
||||||
if err := h.db.Create(&namespace).Error; err != nil {
|
if err := h.db.Create(&namespace).Error; err != nil {
|
||||||
|
@ -67,7 +67,7 @@ func (h *Headscale) CreateNamespace(name string) (*Namespace, error) {
|
||||||
func (h *Headscale) DestroyNamespace(name string) error {
|
func (h *Headscale) DestroyNamespace(name string) error {
|
||||||
namespace, err := h.GetNamespace(name)
|
namespace, err := h.GetNamespace(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errNamespaceNotFound
|
return ErrNamespaceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
machines, err := h.ListMachinesInNamespace(name)
|
machines, err := h.ListMachinesInNamespace(name)
|
||||||
|
@ -75,7 +75,7 @@ func (h *Headscale) DestroyNamespace(name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(machines) > 0 {
|
if len(machines) > 0 {
|
||||||
return errNamespaceNotEmptyOfNodes
|
return ErrNamespaceNotEmptyOfNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
keys, err := h.ListPreAuthKeys(name)
|
keys, err := h.ListPreAuthKeys(name)
|
||||||
|
@ -110,9 +110,9 @@ func (h *Headscale) RenameNamespace(oldName, newName string) error {
|
||||||
}
|
}
|
||||||
_, err = h.GetNamespace(newName)
|
_, err = h.GetNamespace(newName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return errNamespaceExists
|
return ErrNamespaceExists
|
||||||
}
|
}
|
||||||
if !errors.Is(err, errNamespaceNotFound) {
|
if !errors.Is(err, ErrNamespaceNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ func (h *Headscale) GetNamespace(name string) (*Namespace, error) {
|
||||||
result.Error,
|
result.Error,
|
||||||
gorm.ErrRecordNotFound,
|
gorm.ErrRecordNotFound,
|
||||||
) {
|
) {
|
||||||
return nil, errNamespaceNotFound
|
return nil, ErrNamespaceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return &namespace, nil
|
return &namespace, nil
|
||||||
|
@ -272,7 +272,7 @@ func NormalizeToFQDNRules(name string, stripEmailDomain bool) (string, error) {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"label %v is more than 63 chars: %w",
|
"label %v is more than 63 chars: %w",
|
||||||
elt,
|
elt,
|
||||||
errInvalidNamespaceName,
|
ErrInvalidNamespaceName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,21 +285,21 @@ func CheckForFQDNRules(name string) error {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"DNS segment must not be over 63 chars. %v doesn't comply with this rule: %w",
|
"DNS segment must not be over 63 chars. %v doesn't comply with this rule: %w",
|
||||||
name,
|
name,
|
||||||
errInvalidNamespaceName,
|
ErrInvalidNamespaceName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if strings.ToLower(name) != name {
|
if strings.ToLower(name) != name {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"DNS segment should be lowercase. %v doesn't comply with this rule: %w",
|
"DNS segment should be lowercase. %v doesn't comply with this rule: %w",
|
||||||
name,
|
name,
|
||||||
errInvalidNamespaceName,
|
ErrInvalidNamespaceName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if invalidCharsInNamespaceRegex.MatchString(name) {
|
if invalidCharsInNamespaceRegex.MatchString(name) {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"DNS segment should only be composed of lowercase ASCII letters numbers, hyphen and dots. %v doesn't comply with theses rules: %w",
|
"DNS segment should only be composed of lowercase ASCII letters numbers, hyphen and dots. %v doesn't comply with theses rules: %w",
|
||||||
name,
|
name,
|
||||||
errInvalidNamespaceName,
|
ErrInvalidNamespaceName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ func (s *Suite) TestCreateAndDestroyNamespace(c *check.C) {
|
||||||
|
|
||||||
func (s *Suite) TestDestroyNamespaceErrors(c *check.C) {
|
func (s *Suite) TestDestroyNamespaceErrors(c *check.C) {
|
||||||
err := app.DestroyNamespace("test")
|
err := app.DestroyNamespace("test")
|
||||||
c.Assert(err, check.Equals, errNamespaceNotFound)
|
c.Assert(err, check.Equals, ErrNamespaceNotFound)
|
||||||
|
|
||||||
namespace, err := app.CreateNamespace("test")
|
namespace, err := app.CreateNamespace("test")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
@ -60,7 +60,7 @@ func (s *Suite) TestDestroyNamespaceErrors(c *check.C) {
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
err = app.DestroyNamespace("test")
|
err = app.DestroyNamespace("test")
|
||||||
c.Assert(err, check.Equals, errNamespaceNotEmptyOfNodes)
|
c.Assert(err, check.Equals, ErrNamespaceNotEmptyOfNodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestRenameNamespace(c *check.C) {
|
func (s *Suite) TestRenameNamespace(c *check.C) {
|
||||||
|
@ -76,20 +76,20 @@ func (s *Suite) TestRenameNamespace(c *check.C) {
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
_, err = app.GetNamespace("test")
|
_, err = app.GetNamespace("test")
|
||||||
c.Assert(err, check.Equals, errNamespaceNotFound)
|
c.Assert(err, check.Equals, ErrNamespaceNotFound)
|
||||||
|
|
||||||
_, err = app.GetNamespace("test-renamed")
|
_, err = app.GetNamespace("test-renamed")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
err = app.RenameNamespace("test-does-not-exit", "test")
|
err = app.RenameNamespace("test-does-not-exit", "test")
|
||||||
c.Assert(err, check.Equals, errNamespaceNotFound)
|
c.Assert(err, check.Equals, ErrNamespaceNotFound)
|
||||||
|
|
||||||
namespaceTest2, err := app.CreateNamespace("test2")
|
namespaceTest2, err := app.CreateNamespace("test2")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(namespaceTest2.Name, check.Equals, "test2")
|
c.Assert(namespaceTest2.Name, check.Equals, "test2")
|
||||||
|
|
||||||
err = app.RenameNamespace("test2", "test-renamed")
|
err = app.RenameNamespace("test2", "test-renamed")
|
||||||
c.Assert(err, check.Equals, errNamespaceExists)
|
c.Assert(err, check.Equals, ErrNamespaceExists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
func (s *Suite) TestGetMapResponseUserProfiles(c *check.C) {
|
||||||
|
@ -402,7 +402,7 @@ func (s *Suite) TestSetMachineNamespace(c *check.C) {
|
||||||
c.Assert(machine.Namespace.Name, check.Equals, newNamespace.Name)
|
c.Assert(machine.Namespace.Name, check.Equals, newNamespace.Name)
|
||||||
|
|
||||||
err = app.SetMachineNamespace(&machine, "non-existing-namespace")
|
err = app.SetMachineNamespace(&machine, "non-existing-namespace")
|
||||||
c.Assert(err, check.Equals, errNamespaceNotFound)
|
c.Assert(err, check.Equals, ErrNamespaceNotFound)
|
||||||
|
|
||||||
err = app.SetMachineNamespace(&machine, newNamespace.Name)
|
err = app.SetMachineNamespace(&machine, newNamespace.Name)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
2
oidc.go
2
oidc.go
|
@ -416,7 +416,7 @@ func (h *Headscale) OIDCCallback(
|
||||||
log.Debug().Msg("Registering new machine after successful callback")
|
log.Debug().Msg("Registering new machine after successful callback")
|
||||||
|
|
||||||
namespace, err := h.GetNamespace(namespaceName)
|
namespace, err := h.GetNamespace(namespaceName)
|
||||||
if errors.Is(err, errNamespaceNotFound) {
|
if errors.Is(err, ErrNamespaceNotFound) {
|
||||||
namespace, err = h.CreateNamespace(namespaceName)
|
namespace, err = h.CreateNamespace(namespaceName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -14,10 +14,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errPreAuthKeyNotFound = Error("AuthKey not found")
|
ErrPreAuthKeyNotFound = Error("AuthKey not found")
|
||||||
errPreAuthKeyExpired = Error("AuthKey expired")
|
ErrPreAuthKeyExpired = Error("AuthKey expired")
|
||||||
errSingleUseAuthKeyHasBeenUsed = Error("AuthKey has already been used")
|
ErrSingleUseAuthKeyHasBeenUsed = Error("AuthKey has already been used")
|
||||||
errNamespaceMismatch = Error("namespace mismatch")
|
ErrNamespaceMismatch = Error("namespace mismatch")
|
||||||
)
|
)
|
||||||
|
|
||||||
// PreAuthKey describes a pre-authorization key usable in a particular namespace.
|
// PreAuthKey describes a pre-authorization key usable in a particular namespace.
|
||||||
|
@ -92,7 +92,7 @@ func (h *Headscale) GetPreAuthKey(namespace string, key string) (*PreAuthKey, er
|
||||||
}
|
}
|
||||||
|
|
||||||
if pak.Namespace.Name != namespace {
|
if pak.Namespace.Name != namespace {
|
||||||
return nil, errNamespaceMismatch
|
return nil, ErrNamespaceMismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
return pak, nil
|
return pak, nil
|
||||||
|
@ -135,11 +135,11 @@ func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
|
||||||
result.Error,
|
result.Error,
|
||||||
gorm.ErrRecordNotFound,
|
gorm.ErrRecordNotFound,
|
||||||
) {
|
) {
|
||||||
return nil, errPreAuthKeyNotFound
|
return nil, ErrPreAuthKeyNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if pak.Expiration != nil && pak.Expiration.Before(time.Now()) {
|
if pak.Expiration != nil && pak.Expiration.Before(time.Now()) {
|
||||||
return nil, errPreAuthKeyExpired
|
return nil, ErrPreAuthKeyExpired
|
||||||
}
|
}
|
||||||
|
|
||||||
if pak.Reusable || pak.Ephemeral { // we don't need to check if has been used before
|
if pak.Reusable || pak.Ephemeral { // we don't need to check if has been used before
|
||||||
|
@ -152,7 +152,7 @@ func (h *Headscale) checkKeyValidity(k string) (*PreAuthKey, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(machines) != 0 || pak.Used {
|
if len(machines) != 0 || pak.Used {
|
||||||
return nil, errSingleUseAuthKeyHasBeenUsed
|
return nil, ErrSingleUseAuthKeyHasBeenUsed
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pak, nil
|
return &pak, nil
|
||||||
|
|
|
@ -44,13 +44,13 @@ func (*Suite) TestExpiredPreAuthKey(c *check.C) {
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
key, err := app.checkKeyValidity(pak.Key)
|
key, err := app.checkKeyValidity(pak.Key)
|
||||||
c.Assert(err, check.Equals, errPreAuthKeyExpired)
|
c.Assert(err, check.Equals, ErrPreAuthKeyExpired)
|
||||||
c.Assert(key, check.IsNil)
|
c.Assert(key, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Suite) TestPreAuthKeyDoesNotExist(c *check.C) {
|
func (*Suite) TestPreAuthKeyDoesNotExist(c *check.C) {
|
||||||
key, err := app.checkKeyValidity("potatoKey")
|
key, err := app.checkKeyValidity("potatoKey")
|
||||||
c.Assert(err, check.Equals, errPreAuthKeyNotFound)
|
c.Assert(err, check.Equals, ErrPreAuthKeyNotFound)
|
||||||
c.Assert(key, check.IsNil)
|
c.Assert(key, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ func (*Suite) TestAlreadyUsedKey(c *check.C) {
|
||||||
app.db.Save(&machine)
|
app.db.Save(&machine)
|
||||||
|
|
||||||
key, err := app.checkKeyValidity(pak.Key)
|
key, err := app.checkKeyValidity(pak.Key)
|
||||||
c.Assert(err, check.Equals, errSingleUseAuthKeyHasBeenUsed)
|
c.Assert(err, check.Equals, ErrSingleUseAuthKeyHasBeenUsed)
|
||||||
c.Assert(key, check.IsNil)
|
c.Assert(key, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ func (*Suite) TestExpirePreauthKey(c *check.C) {
|
||||||
c.Assert(pak.Expiration, check.NotNil)
|
c.Assert(pak.Expiration, check.NotNil)
|
||||||
|
|
||||||
key, err := app.checkKeyValidity(pak.Key)
|
key, err := app.checkKeyValidity(pak.Key)
|
||||||
c.Assert(err, check.Equals, errPreAuthKeyExpired)
|
c.Assert(err, check.Equals, ErrPreAuthKeyExpired)
|
||||||
c.Assert(key, check.IsNil)
|
c.Assert(key, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,5 +188,5 @@ func (*Suite) TestNotReusableMarkedAsUsed(c *check.C) {
|
||||||
app.db.Save(&pak)
|
app.db.Save(&pak)
|
||||||
|
|
||||||
_, err = app.checkKeyValidity(pak.Key)
|
_, err = app.checkKeyValidity(pak.Key)
|
||||||
c.Assert(err, check.Equals, errSingleUseAuthKeyHasBeenUsed)
|
c.Assert(err, check.Equals, ErrSingleUseAuthKeyHasBeenUsed)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errRouteIsNotAvailable = Error("route is not available")
|
ErrRouteIsNotAvailable = Error("route is not available")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Deprecated: use machine function instead
|
// Deprecated: use machine function instead
|
||||||
|
@ -106,7 +106,7 @@ func (h *Headscale) EnableNodeRoute(
|
||||||
}
|
}
|
||||||
|
|
||||||
if !available {
|
if !available {
|
||||||
return errRouteIsNotAvailable
|
return ErrRouteIsNotAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
machine.EnabledRoutes = enabledRoutes
|
machine.EnabledRoutes = enabledRoutes
|
||||||
|
|
8
utils.go
8
utils.go
|
@ -27,8 +27,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errCannotDecryptReponse = Error("cannot decrypt response")
|
ErrCannotDecryptResponse = Error("cannot decrypt response")
|
||||||
errCouldNotAllocateIP = Error("could not find any suitable IP")
|
ErrCouldNotAllocateIP = Error("could not find any suitable IP")
|
||||||
|
|
||||||
// These constants are copied from the upstream tailscale.com/types/key
|
// These constants are copied from the upstream tailscale.com/types/key
|
||||||
// library, because they are not exported.
|
// library, because they are not exported.
|
||||||
|
@ -120,7 +120,7 @@ func decode(
|
||||||
|
|
||||||
decrypted, ok := privKey.OpenFrom(*pubKey, msg)
|
decrypted, ok := privKey.OpenFrom(*pubKey, msg)
|
||||||
if !ok {
|
if !ok {
|
||||||
return errCannotDecryptReponse
|
return ErrCannotDecryptResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(decrypted, output); err != nil {
|
if err := json.Unmarshal(decrypted, output); err != nil {
|
||||||
|
@ -181,7 +181,7 @@ func (h *Headscale) getAvailableIP(ipPrefix netaddr.IPPrefix) (*netaddr.IP, erro
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if !ipPrefix.Contains(ip) {
|
if !ipPrefix.Contains(ip) {
|
||||||
return nil, errCouldNotAllocateIP
|
return nil, ErrCouldNotAllocateIP
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
Loading…
Reference in a new issue