diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d989a84c..02e47425 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,19 +6,24 @@ labels: ["bug"] assignees: "" --- - + + +## Bug description -**To Reproduce** - - - -**Context info** +## Environment + +- OS: +- Headscale version: +- Tailscale version: + + + +- [ ] Headscale is behind a (reverse) proxy +- [ ] Headscale runs in a container + +## To Reproduce + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index a9428c00..92c51b8f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,12 +6,21 @@ labels: ["enhancement"] assignees: "" --- - + - +## Why - + +## Description + + diff --git a/.github/ISSUE_TEMPLATE/other_issue.md b/.github/ISSUE_TEMPLATE/other_issue.md deleted file mode 100644 index 3004a97c..00000000 --- a/.github/ISSUE_TEMPLATE/other_issue.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: "Other issue" -about: "Report a different issue" -title: "" -labels: ["bug"] -assignees: "" ---- - - - - - -**Issue description** - - - -**To Reproduce** - - - -**Context info** - - diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 88289dab..d4e4f4f9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,15 @@ + + - [ ] read the [CONTRIBUTING guidelines](README.md#contributing) diff --git a/.github/renovate.json b/.github/renovate.json index ce38ba96..7b3ef57c 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -6,31 +6,27 @@ "onboarding": false, "extends": ["config:base", ":rebaseStalePrs"], "ignorePresets": [":prHourlyLimit2"], - "enabledManagers": ["dockerfile", "gomod", "github-actions","regex" ], + "enabledManagers": ["dockerfile", "gomod", "github-actions", "regex"], "includeForks": true, "repositories": ["juanfont/headscale"], "platform": "github", "packageRules": [ { - "matchDatasources": ["go"], - "groupName": "Go modules", - "groupSlug": "gomod", - "separateMajorMinor": false + "matchDatasources": ["go"], + "groupName": "Go modules", + "groupSlug": "gomod", + "separateMajorMinor": false }, { - "matchDatasources": ["docker"], - "groupName": "Dockerfiles", - "groupSlug": "dockerfiles" - } + "matchDatasources": ["docker"], + "groupName": "Dockerfiles", + "groupSlug": "dockerfiles" + } ], "regexManagers": [ { - "fileMatch": [ - ".github/workflows/.*.yml$" - ], - "matchStrings": [ - "\\s*go-version:\\s*\"?(?.*?)\"?\\n" - ], + "fileMatch": [".github/workflows/.*.yml$"], + "matchStrings": ["\\s*go-version:\\s*\"?(?.*?)\"?\\n"], "datasourceTemplate": "golang-version", "depNameTemplate": "actions/go-version" } diff --git a/.github/workflows/test-integration-cli.yml b/.github/workflows/test-integration-cli.yml deleted file mode 100644 index 72cf31aa..00000000 --- a/.github/workflows/test-integration-cli.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Integration Test CLI - -on: [pull_request] - -jobs: - integration-test-cli: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 2 - - - name: Set Swap Space - uses: pierotofy/set-swap-space@master - with: - swap-size-gb: 10 - - - name: Get changed files - id: changed-files - uses: tj-actions/changed-files@v34 - with: - files: | - *.nix - go.* - **/*.go - integration_test/ - config-example.yaml - - - uses: cachix/install-nix-action@v16 - if: steps.changed-files.outputs.any_changed == 'true' - - - name: Run CLI integration tests - if: steps.changed-files.outputs.any_changed == 'true' - run: nix develop --command -- make test_integration_cli diff --git a/.github/workflows/test-integration-v2-TestACLAllowStarDst.yaml b/.github/workflows/test-integration-v2-TestACLAllowStarDst.yaml index f63a6d2a..1a0487b0 100644 --- a/.github/workflows/test-integration-v2-TestACLAllowStarDst.yaml +++ b/.github/workflows/test-integration-v2-TestACLAllowStarDst.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestACLAllowUser80Dst.yaml b/.github/workflows/test-integration-v2-TestACLAllowUser80Dst.yaml index 3b3dd59e..06e48b83 100644 --- a/.github/workflows/test-integration-v2-TestACLAllowUser80Dst.yaml +++ b/.github/workflows/test-integration-v2-TestACLAllowUser80Dst.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestACLAllowUserDst.yaml b/.github/workflows/test-integration-v2-TestACLAllowUserDst.yaml index 54b425cf..e78049b2 100644 --- a/.github/workflows/test-integration-v2-TestACLAllowUserDst.yaml +++ b/.github/workflows/test-integration-v2-TestACLAllowUserDst.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml b/.github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml index ed01bf71..79df71d5 100644 --- a/.github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml +++ b/.github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestACLDevice1CanAccessDevice2.yaml b/.github/workflows/test-integration-v2-TestACLDevice1CanAccessDevice2.yaml index 4efbf1a5..8a36a2d2 100644 --- a/.github/workflows/test-integration-v2-TestACLDevice1CanAccessDevice2.yaml +++ b/.github/workflows/test-integration-v2-TestACLDevice1CanAccessDevice2.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestACLHostsInNetMapTable.yaml b/.github/workflows/test-integration-v2-TestACLHostsInNetMapTable.yaml index 7445695c..c20cdf68 100644 --- a/.github/workflows/test-integration-v2-TestACLHostsInNetMapTable.yaml +++ b/.github/workflows/test-integration-v2-TestACLHostsInNetMapTable.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestACLNamedHostsCanReach.yaml b/.github/workflows/test-integration-v2-TestACLNamedHostsCanReach.yaml index f0f663ca..d1e4c9d1 100644 --- a/.github/workflows/test-integration-v2-TestACLNamedHostsCanReach.yaml +++ b/.github/workflows/test-integration-v2-TestACLNamedHostsCanReach.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestACLNamedHostsCanReachBySubnet.yaml b/.github/workflows/test-integration-v2-TestACLNamedHostsCanReachBySubnet.yaml index daeb6eee..344ae518 100644 --- a/.github/workflows/test-integration-v2-TestACLNamedHostsCanReachBySubnet.yaml +++ b/.github/workflows/test-integration-v2-TestACLNamedHostsCanReachBySubnet.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestAuthKeyLogoutAndRelogin.yaml b/.github/workflows/test-integration-v2-TestAuthKeyLogoutAndRelogin.yaml index c603d4fe..675f7dd0 100644 --- a/.github/workflows/test-integration-v2-TestAuthKeyLogoutAndRelogin.yaml +++ b/.github/workflows/test-integration-v2-TestAuthKeyLogoutAndRelogin.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestAuthWebFlowAuthenticationPingAll.yaml b/.github/workflows/test-integration-v2-TestAuthWebFlowAuthenticationPingAll.yaml index 19d99017..984eb650 100644 --- a/.github/workflows/test-integration-v2-TestAuthWebFlowAuthenticationPingAll.yaml +++ b/.github/workflows/test-integration-v2-TestAuthWebFlowAuthenticationPingAll.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestAuthWebFlowLogoutAndRelogin.yaml b/.github/workflows/test-integration-v2-TestAuthWebFlowLogoutAndRelogin.yaml index a9eeb403..47177d49 100644 --- a/.github/workflows/test-integration-v2-TestAuthWebFlowLogoutAndRelogin.yaml +++ b/.github/workflows/test-integration-v2-TestAuthWebFlowLogoutAndRelogin.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestCreateTailscale.yaml b/.github/workflows/test-integration-v2-TestCreateTailscale.yaml index e342de61..47a5157d 100644 --- a/.github/workflows/test-integration-v2-TestCreateTailscale.yaml +++ b/.github/workflows/test-integration-v2-TestCreateTailscale.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestDERPServerScenario.yaml b/.github/workflows/test-integration-v2-TestDERPServerScenario.yaml index b3246c27..0dfaeb75 100644 --- a/.github/workflows/test-integration-v2-TestDERPServerScenario.yaml +++ b/.github/workflows/test-integration-v2-TestDERPServerScenario.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestEnablingRoutes.yaml b/.github/workflows/test-integration-v2-TestEnablingRoutes.yaml index f6607736..aeb86428 100644 --- a/.github/workflows/test-integration-v2-TestEnablingRoutes.yaml +++ b/.github/workflows/test-integration-v2-TestEnablingRoutes.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestEphemeral.yaml b/.github/workflows/test-integration-v2-TestEphemeral.yaml index f3af06ac..e81e0937 100644 --- a/.github/workflows/test-integration-v2-TestEphemeral.yaml +++ b/.github/workflows/test-integration-v2-TestEphemeral.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestExpireNode.yaml b/.github/workflows/test-integration-v2-TestExpireNode.yaml index 9daac7e1..80af2608 100644 --- a/.github/workflows/test-integration-v2-TestExpireNode.yaml +++ b/.github/workflows/test-integration-v2-TestExpireNode.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestHeadscale.yaml b/.github/workflows/test-integration-v2-TestHeadscale.yaml index d93a54c9..52b562df 100644 --- a/.github/workflows/test-integration-v2-TestHeadscale.yaml +++ b/.github/workflows/test-integration-v2-TestHeadscale.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestOIDCAuthenticationPingAll.yaml b/.github/workflows/test-integration-v2-TestOIDCAuthenticationPingAll.yaml index 003319e6..e9a4e1ca 100644 --- a/.github/workflows/test-integration-v2-TestOIDCAuthenticationPingAll.yaml +++ b/.github/workflows/test-integration-v2-TestOIDCAuthenticationPingAll.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestOIDCExpireNodesBasedOnTokenExpiry.yaml b/.github/workflows/test-integration-v2-TestOIDCExpireNodesBasedOnTokenExpiry.yaml index cd8d9c34..f9068888 100644 --- a/.github/workflows/test-integration-v2-TestOIDCExpireNodesBasedOnTokenExpiry.yaml +++ b/.github/workflows/test-integration-v2-TestOIDCExpireNodesBasedOnTokenExpiry.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestPingAllByHostname.yaml b/.github/workflows/test-integration-v2-TestPingAllByHostname.yaml index 12633c80..60e9fbaf 100644 --- a/.github/workflows/test-integration-v2-TestPingAllByHostname.yaml +++ b/.github/workflows/test-integration-v2-TestPingAllByHostname.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestPingAllByIP.yaml b/.github/workflows/test-integration-v2-TestPingAllByIP.yaml index 834fce94..05413e86 100644 --- a/.github/workflows/test-integration-v2-TestPingAllByIP.yaml +++ b/.github/workflows/test-integration-v2-TestPingAllByIP.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestPreAuthKeyCommand.yaml b/.github/workflows/test-integration-v2-TestPreAuthKeyCommand.yaml index 6bc5ded6..f828b51d 100644 --- a/.github/workflows/test-integration-v2-TestPreAuthKeyCommand.yaml +++ b/.github/workflows/test-integration-v2-TestPreAuthKeyCommand.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestPreAuthKeyCommandReusableEphemeral.yaml b/.github/workflows/test-integration-v2-TestPreAuthKeyCommandReusableEphemeral.yaml index 003128b6..2114bd89 100644 --- a/.github/workflows/test-integration-v2-TestPreAuthKeyCommandReusableEphemeral.yaml +++ b/.github/workflows/test-integration-v2-TestPreAuthKeyCommandReusableEphemeral.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestPreAuthKeyCommandWithoutExpiry.yaml b/.github/workflows/test-integration-v2-TestPreAuthKeyCommandWithoutExpiry.yaml index 619b2647..53cb3957 100644 --- a/.github/workflows/test-integration-v2-TestPreAuthKeyCommandWithoutExpiry.yaml +++ b/.github/workflows/test-integration-v2-TestPreAuthKeyCommandWithoutExpiry.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestResolveMagicDNS.yaml b/.github/workflows/test-integration-v2-TestResolveMagicDNS.yaml index ad6aba62..ac2b0b92 100644 --- a/.github/workflows/test-integration-v2-TestResolveMagicDNS.yaml +++ b/.github/workflows/test-integration-v2-TestResolveMagicDNS.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestSSHIsBlockedInACL.yaml b/.github/workflows/test-integration-v2-TestSSHIsBlockedInACL.yaml index 5664151b..5ee979b4 100644 --- a/.github/workflows/test-integration-v2-TestSSHIsBlockedInACL.yaml +++ b/.github/workflows/test-integration-v2-TestSSHIsBlockedInACL.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestSSHMultipleUsersAllToAll.yaml b/.github/workflows/test-integration-v2-TestSSHMultipleUsersAllToAll.yaml index 286ad486..bcc88863 100644 --- a/.github/workflows/test-integration-v2-TestSSHMultipleUsersAllToAll.yaml +++ b/.github/workflows/test-integration-v2-TestSSHMultipleUsersAllToAll.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestSSHNoSSHConfigured.yaml b/.github/workflows/test-integration-v2-TestSSHNoSSHConfigured.yaml index e4fa04a6..bf641a0e 100644 --- a/.github/workflows/test-integration-v2-TestSSHNoSSHConfigured.yaml +++ b/.github/workflows/test-integration-v2-TestSSHNoSSHConfigured.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestSSHOneUserAllToAll.yaml b/.github/workflows/test-integration-v2-TestSSHOneUserAllToAll.yaml index 3ef3e991..d61378de 100644 --- a/.github/workflows/test-integration-v2-TestSSHOneUserAllToAll.yaml +++ b/.github/workflows/test-integration-v2-TestSSHOneUserAllToAll.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestSSUserOnlyIsolation.yaml b/.github/workflows/test-integration-v2-TestSSUserOnlyIsolation.yaml index 52fd09a4..a40a3af0 100644 --- a/.github/workflows/test-integration-v2-TestSSUserOnlyIsolation.yaml +++ b/.github/workflows/test-integration-v2-TestSSUserOnlyIsolation.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestTaildrop.yaml b/.github/workflows/test-integration-v2-TestTaildrop.yaml index 97601239..1d6c2430 100644 --- a/.github/workflows/test-integration-v2-TestTaildrop.yaml +++ b/.github/workflows/test-integration-v2-TestTaildrop.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestTailscaleNodesJoiningHeadcale.yaml b/.github/workflows/test-integration-v2-TestTailscaleNodesJoiningHeadcale.yaml index ca70dd72..941c2311 100644 --- a/.github/workflows/test-integration-v2-TestTailscaleNodesJoiningHeadcale.yaml +++ b/.github/workflows/test-integration-v2-TestTailscaleNodesJoiningHeadcale.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/.github/workflows/test-integration-v2-TestUserCommand.yaml b/.github/workflows/test-integration-v2-TestUserCommand.yaml index 5b37b9e7..449512bf 100644 --- a/.github/workflows/test-integration-v2-TestUserCommand.yaml +++ b/.github/workflows/test-integration-v2-TestUserCommand.yaml @@ -43,7 +43,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ad857d9..f5320307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## 0.23.0 (2023-XX-XX) +### BREAKING + +- Code reorganisation, a lot of code has moved, please review the following PRs accordingly [#1444](https://github.com/juanfont/headscale/pull/1444) + +### Changes + +## 0.22.2 (2023-05-10) + ### Changes - Add environment flags to enable pprof (profiling) [#1382](https://github.com/juanfont/headscale/pull/1382) @@ -9,8 +17,11 @@ - Fix systemd service file location in `.deb` packages [#1391](https://github.com/juanfont/headscale/pull/1391) - Improvements on Noise implementation [#1379](https://github.com/juanfont/headscale/pull/1379) - Replace node filter logic, ensuring nodes with access can see eachother [#1381](https://github.com/juanfont/headscale/pull/1381) +- Disable (or delete) both exit routes at the same time [#1428](https://github.com/juanfont/headscale/pull/1428) +- Ditch distroless for Docker image, create default socket dir in `/var/run/headscale` [#1450](https://github.com/juanfont/headscale/pull/1450) - Updated docker related doc, added example docker compose file [#1421](https://github.com/juanfont/headscale/pull/1421) + ## 0.22.1 (2023-04-20) ### Changes diff --git a/Dockerfile b/Dockerfile index a85c1220..4d7d6a58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,10 +14,12 @@ RUN strip /go/bin/headscale RUN test -e /go/bin/headscale # Production image -FROM gcr.io/distroless/base-debian11 +FROM docker.io/debian:bullseye-slim COPY --from=build /go/bin/headscale /bin/headscale ENV TZ UTC +RUN mkdir -p /var/run/headscale + EXPOSE 8080/tcp CMD ["headscale"] diff --git a/Dockerfile.debug b/Dockerfile.debug index 3a751ecd..7cd609cf 100644 --- a/Dockerfile.debug +++ b/Dockerfile.debug @@ -18,6 +18,8 @@ FROM docker.io/golang:1.20.0-bullseye COPY --from=build /go/bin/headscale /bin/headscale ENV TZ UTC +RUN mkdir -p /var/run/headscale + # Need to reset the entrypoint or everything will run as a busybox script ENTRYPOINT [] EXPOSE 8080/tcp diff --git a/Makefile b/Makefile index a4b0f7da..4fdf418e 100644 --- a/Makefile +++ b/Makefile @@ -24,21 +24,9 @@ build: dev: lint test build test: - @go test $(TAGS) -short -coverprofile=coverage.out ./... + gotestsum -- $(TAGS) -short -coverprofile=coverage.out ./... -test_integration: test_integration_cli test_integration_derp test_integration_v2_general - -test_integration_cli: - docker network rm $$(docker network ls --filter name=headscale --quiet) || true - docker network create headscale-test || true - docker run -t --rm \ - --network headscale-test \ - -v ~/.cache/hs-integration-go:/go \ - -v $$PWD:$$PWD -w $$PWD \ - -v /var/run/docker.sock:/var/run/docker.sock golang:1 \ - go run gotest.tools/gotestsum@latest -- $(TAGS) -failfast -timeout 30m -count=1 -run IntegrationCLI ./... - -test_integration_v2_general: +test_integration: docker run \ -t --rm \ -v ~/.cache/hs-integration-go:/go \ diff --git a/README.md b/README.md index ac72a9d8..200c8cde 100644 --- a/README.md +++ b/README.md @@ -32,21 +32,18 @@ organisation. ## Design goal -`headscale` aims to implement a self-hosted, open source alternative to the Tailscale -control server. `headscale` has a narrower scope and an instance of `headscale` -implements a _single_ Tailnet, which is typically what a single organisation, or -home/personal setup would use. +Headscale aims to implement a self-hosted, open source alternative to the Tailscale +control server. +Headscale's goal is to provide self-hosters and hobbyists with an open-source +server they can use for their projects and labs. +It implements a narrow scope, a single Tailnet, suitable for a personal use, or a small +open-source organisation. -`headscale` uses terms that maps to Tailscale's control server, consult the - -## Support +## Supporting Headscale If you like `headscale` and find it useful, there is a sponsorship and donation buttons available in the repo. -If you would like to sponsor features, bugs or prioritisation, reach out to -one of the maintainers. - ## Features - Full "base" support of Tailscale's features @@ -78,17 +75,11 @@ one of the maintainers. ## Running headscale +**Please note that we do not support nor encourage the use of reverse proxies +and container to run Headscale.** + Please have a look at the [`documentation`](https://headscale.net/). -## Graphical Control Panels - -Headscale provides an API for complete management of your Tailnet. -These are community projects not directly affiliated with the Headscale project. - -| Name | Repository Link | Description | Status | -| --------------- | ---------------------------------------------------- | ------------------------------------------------------ | ------ | -| headscale-webui | [Github](https://github.com/ifargle/headscale-webui) | A simple Headscale web UI for small-scale deployments. | Alpha | - ## Talks - Fosdem 2023 (video): [Headscale: How we are using integration testing to reimplement Tailscale](https://fosdem.org/2023/schedule/event/goheadscale/) @@ -96,11 +87,23 @@ These are community projects not directly affiliated with the Headscale project. ## Disclaimer -1. We have nothing to do with Tailscale, or Tailscale Inc. +1. This project is not associated with Tailscale Inc. 2. The purpose of Headscale is maintaining a working, self-hosted Tailscale control panel. ## Contributing +Headscale is "Open Source, acknowledged contribution", this means that any +contribution will have to be discussed with the Maintainers before being submitted. + +This model has been chosen to reduce the risk of burnout by limiting the +maintenance overhead of reviewing and validating third-party code. + +Headscale is open to code contributions for bug fixes without discussion. + +If you find mistakes in the documentation, please submit a fix to the documentation. + +### Requirements + To contribute to headscale you would need the lastest version of [Go](https://golang.org) and [Buf](https://buf.build)(Protobuf generator). @@ -108,8 +111,6 @@ We recommend using [Nix](https://nixos.org/) to setup a development environment. be done with `nix develop`, which will install the tools and give you a shell. This guarantees that you will have the same dev env as `headscale` maintainers. -PRs and suggestions are welcome. - ### Code style To ensure we have some consistency with a growing number of contributions, diff --git a/cmd/gh-action-integration-generator/main.go b/cmd/gh-action-integration-generator/main.go index cc0e7c92..0b363b0c 100644 --- a/cmd/gh-action-integration-generator/main.go +++ b/cmd/gh-action-integration-generator/main.go @@ -64,7 +64,7 @@ jobs: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume $PWD/control_logs:/tmp/control \ golang:1 \ - go test ./... \ + go run gotest.tools/gotestsum@latest -- ./... \ -tags ts2019 \ -failfast \ -timeout 120m \ diff --git a/cmd/headscale/cli/api_key.go b/cmd/headscale/cli/api_key.go index 5756db48..f7c7e3a2 100644 --- a/cmd/headscale/cli/api_key.go +++ b/cmd/headscale/cli/api_key.go @@ -5,8 +5,8 @@ import ( "strconv" "time" - "github.com/juanfont/headscale" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol" "github.com/prometheus/common/model" "github.com/pterm/pterm" "github.com/rs/zerolog/log" @@ -83,7 +83,7 @@ var listAPIKeys = &cobra.Command{ } tableData = append(tableData, []string{ - strconv.FormatUint(key.GetId(), headscale.Base10), + strconv.FormatUint(key.GetId(), hscontrol.Base10), key.GetPrefix(), expiration, key.GetCreatedAt().AsTime().Format(HeadscaleDateTimeFormat), diff --git a/cmd/headscale/cli/debug.go b/cmd/headscale/cli/debug.go index 383ed13c..f2c8028f 100644 --- a/cmd/headscale/cli/debug.go +++ b/cmd/headscale/cli/debug.go @@ -3,8 +3,8 @@ package cli import ( "fmt" - "github.com/juanfont/headscale" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "google.golang.org/grpc/status" @@ -93,7 +93,7 @@ var createNodeCmd = &cobra.Command{ return } - if !headscale.NodePublicKeyRegex.Match([]byte(machineKey)) { + if !hscontrol.NodePublicKeyRegex.Match([]byte(machineKey)) { err = errPreAuthKeyMalformed ErrorOutput( err, diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index 5d8babd8..772b428e 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -9,8 +9,8 @@ import ( "time" survey "github.com/AlecAivazis/survey/v2" - "github.com/juanfont/headscale" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol" "github.com/pterm/pterm" "github.com/spf13/cobra" "google.golang.org/grpc/status" @@ -529,7 +529,7 @@ func nodesToPtables( var machineKey key.MachinePublic err := machineKey.UnmarshalText( - []byte(headscale.MachinePublicKeyEnsurePrefix(machine.MachineKey)), + []byte(hscontrol.MachinePublicKeyEnsurePrefix(machine.MachineKey)), ) if err != nil { machineKey = key.MachinePublic{} @@ -537,7 +537,7 @@ func nodesToPtables( var nodeKey key.NodePublic err = nodeKey.UnmarshalText( - []byte(headscale.NodePublicKeyEnsurePrefix(machine.NodeKey)), + []byte(hscontrol.NodePublicKeyEnsurePrefix(machine.NodeKey)), ) if err != nil { return nil, err @@ -596,7 +596,7 @@ func nodesToPtables( } nodeData := []string{ - strconv.FormatUint(machine.Id, headscale.Base10), + strconv.FormatUint(machine.Id, hscontrol.Base10), machine.Name, machine.GetGivenName(), machineKey.ShortString(), diff --git a/cmd/headscale/cli/root.go b/cmd/headscale/cli/root.go index cf173f56..ab76fff5 100644 --- a/cmd/headscale/cli/root.go +++ b/cmd/headscale/cli/root.go @@ -5,7 +5,7 @@ import ( "os" "runtime" - "github.com/juanfont/headscale" + "github.com/juanfont/headscale/hscontrol" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -38,18 +38,18 @@ func initConfig() { cfgFile = os.Getenv("HEADSCALE_CONFIG") } if cfgFile != "" { - err := headscale.LoadConfig(cfgFile, true) + err := hscontrol.LoadConfig(cfgFile, true) if err != nil { log.Fatal().Caller().Err(err).Msgf("Error loading config file %s", cfgFile) } } else { - err := headscale.LoadConfig("", false) + err := hscontrol.LoadConfig("", false) if err != nil { log.Fatal().Caller().Err(err).Msgf("Error loading config") } } - cfg, err := headscale.GetHeadscaleConfig() + cfg, err := hscontrol.GetHeadscaleConfig() if err != nil { log.Fatal().Caller().Err(err) } @@ -64,7 +64,7 @@ func initConfig() { zerolog.SetGlobalLevel(zerolog.Disabled) } - if cfg.Log.Format == headscale.JSONLogFormat { + if cfg.Log.Format == hscontrol.JSONLogFormat { log.Logger = log.Output(os.Stdout) } diff --git a/cmd/headscale/cli/routes.go b/cmd/headscale/cli/routes.go index 55f009e9..206209d9 100644 --- a/cmd/headscale/cli/routes.go +++ b/cmd/headscale/cli/routes.go @@ -6,8 +6,8 @@ import ( "net/netip" "strconv" - "github.com/juanfont/headscale" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol" "github.com/pterm/pterm" "github.com/spf13/cobra" "google.golang.org/grpc/status" @@ -277,7 +277,7 @@ func routesToPtables(routes []*v1.Route) pterm.TableData { continue } - if prefix == headscale.ExitRouteV4 || prefix == headscale.ExitRouteV6 { + if prefix == hscontrol.ExitRouteV4 || prefix == hscontrol.ExitRouteV6 { isPrimaryStr = "-" } else { isPrimaryStr = strconv.FormatBool(route.IsPrimary) diff --git a/cmd/headscale/cli/users.go b/cmd/headscale/cli/users.go index 653ab537..3724fe98 100644 --- a/cmd/headscale/cli/users.go +++ b/cmd/headscale/cli/users.go @@ -4,8 +4,8 @@ import ( "fmt" survey "github.com/AlecAivazis/survey/v2" - "github.com/juanfont/headscale" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol" "github.com/pterm/pterm" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -21,7 +21,7 @@ func init() { } const ( - errMissingParameter = headscale.Error("missing parameters") + errMissingParameter = hscontrol.Error("missing parameters") ) var userCmd = &cobra.Command{ diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 3b3ac215..a2a5d592 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -8,8 +8,8 @@ import ( "os" "reflect" - "github.com/juanfont/headscale" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol" "github.com/rs/zerolog/log" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -22,8 +22,8 @@ const ( SocketWritePermissions = 0o666 ) -func getHeadscaleApp() (*headscale.Headscale, error) { - cfg, err := headscale.GetHeadscaleConfig() +func getHeadscaleApp() (*hscontrol.Headscale, error) { + cfg, err := hscontrol.GetHeadscaleConfig() if err != nil { return nil, fmt.Errorf( "failed to load configuration while creating headscale instance: %w", @@ -31,7 +31,7 @@ func getHeadscaleApp() (*headscale.Headscale, error) { ) } - app, err := headscale.NewHeadscale(cfg) + app, err := hscontrol.NewHeadscale(cfg) if err != nil { return nil, err } @@ -39,8 +39,8 @@ func getHeadscaleApp() (*headscale.Headscale, error) { // We are doing this here, as in the future could be cool to have it also hot-reload if cfg.ACL.PolicyPath != "" { - aclPath := headscale.AbsolutePathFromConfigPath(cfg.ACL.PolicyPath) - err = app.LoadACLPolicy(aclPath) + aclPath := hscontrol.AbsolutePathFromConfigPath(cfg.ACL.PolicyPath) + err = app.LoadACLPolicyFromPath(aclPath) if err != nil { log.Fatal(). Str("path", aclPath). @@ -53,7 +53,7 @@ func getHeadscaleApp() (*headscale.Headscale, error) { } func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc.ClientConn, context.CancelFunc) { - cfg, err := headscale.GetHeadscaleConfig() + cfg, err := hscontrol.GetHeadscaleConfig() if err != nil { log.Fatal(). Err(err). @@ -74,7 +74,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. address := cfg.CLI.Address - // If the address is not set, we assume that we are on the server hosting headscale. + // If the address is not set, we assume that we are on the server hosting hscontrol. if address == "" { log.Debug(). Str("socket", cfg.UnixSocket). @@ -98,7 +98,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. grpcOptions = append( grpcOptions, grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithContextDialer(headscale.GrpcSocketDialer), + grpc.WithContextDialer(hscontrol.GrpcSocketDialer), ) } else { // If we are not connecting to a local server, require an API key for authentication diff --git a/cmd/headscale/headscale_test.go b/cmd/headscale/headscale_test.go index c7b332aa..1b987313 100644 --- a/cmd/headscale/headscale_test.go +++ b/cmd/headscale/headscale_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/juanfont/headscale" + "github.com/juanfont/headscale/hscontrol" "github.com/spf13/viper" "gopkg.in/check.v1" ) @@ -50,7 +50,7 @@ func (*Suite) TestConfigFileLoading(c *check.C) { } // Load example config, it should load without validation errors - err = headscale.LoadConfig(cfgFile, true) + err = hscontrol.LoadConfig(cfgFile, true) c.Assert(err, check.IsNil) // Test that config file was interpreted correctly @@ -64,7 +64,7 @@ func (*Suite) TestConfigFileLoading(c *check.C) { 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( - headscale.GetFileMode("unix_socket_permission"), + hscontrol.GetFileMode("unix_socket_permission"), check.Equals, fs.FileMode(0o770), ) @@ -93,7 +93,7 @@ func (*Suite) TestConfigLoading(c *check.C) { } // Load example config, it should load without validation errors - err = headscale.LoadConfig(tmpDir, false) + err = hscontrol.LoadConfig(tmpDir, false) c.Assert(err, check.IsNil) // Test that config file was interpreted correctly @@ -107,7 +107,7 @@ func (*Suite) TestConfigLoading(c *check.C) { 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( - headscale.GetFileMode("unix_socket_permission"), + hscontrol.GetFileMode("unix_socket_permission"), check.Equals, fs.FileMode(0o770), ) @@ -137,10 +137,10 @@ func (*Suite) TestDNSConfigLoading(c *check.C) { } // Load example config, it should load without validation errors - err = headscale.LoadConfig(tmpDir, false) + err = hscontrol.LoadConfig(tmpDir, false) c.Assert(err, check.IsNil) - dnsConfig, baseDomain := headscale.GetDNSConfig() + dnsConfig, baseDomain := hscontrol.GetDNSConfig() c.Assert(dnsConfig.Nameservers[0].String(), check.Equals, "1.1.1.1") c.Assert(dnsConfig.Resolvers[0].Addr, check.Equals, "1.1.1.1") @@ -172,7 +172,7 @@ noise: writeConfig(c, tmpDir, configYaml) // Check configuration validation errors (1) - err = headscale.LoadConfig(tmpDir, false) + err = hscontrol.LoadConfig(tmpDir, false) c.Assert(err, check.NotNil) // check.Matches can not handle multiline strings tmp := strings.ReplaceAll(err.Error(), "\n", "***") @@ -201,6 +201,6 @@ tls_letsencrypt_hostname: example.com tls_letsencrypt_challenge_type: TLS-ALPN-01 `) writeConfig(c, tmpDir, configYaml) - err = headscale.LoadConfig(tmpDir, false) + err = hscontrol.LoadConfig(tmpDir, false) c.Assert(err, check.IsNil) } diff --git a/config-example.yaml b/config-example.yaml index 22cb09a8..99ce552b 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -58,11 +58,12 @@ noise: # List of IP prefixes to allocate tailaddresses from. # Each prefix consists of either an IPv4 or IPv6 address, # and the associated prefix length, delimited by a slash. -# While this looks like it can take arbitrary values, it -# needs to be within IP ranges supported by the Tailscale -# client. +# It must be within IP ranges supported by the Tailscale +# client - i.e., subnets of 100.64.0.0/10 and fd7a:115c:a1e0::/48. +# See below: # IPv6: https://github.com/tailscale/tailscale/blob/22ebb25e833264f58d7c3f534a8b166894a89536/net/tsaddr/tsaddr.go#LL81C52-L81C71 # IPv4: https://github.com/tailscale/tailscale/blob/22ebb25e833264f58d7c3f534a8b166894a89536/net/tsaddr/tsaddr.go#L33 +# Any other range is NOT supported, and it will cause unexpected issues. ip_prefixes: - fd7a:115c:a1e0::/48 - 100.64.0.0/10 @@ -256,7 +257,7 @@ dns_config: # Unix socket used for the CLI to connect without authentication # Note: for production you will want to set this to something like: -unix_socket: /var/run/headscale.sock +unix_socket: /var/run/headscale/headscale.sock unix_socket_permission: "0770" # # headscale supports experimental OpenID connect support, diff --git a/docs/exit-node.md b/docs/exit-node.md index 47eaed1d..898b7811 100644 --- a/docs/exit-node.md +++ b/docs/exit-node.md @@ -14,6 +14,8 @@ If the node is already registered, it can advertise exit capabilities like this: $ sudo tailscale set --advertise-exit-node ``` +To use a node as an exit node, IP forwarding must be enabled on the node. Check the official [Tailscale documentation](https://tailscale.com/kb/1019/subnets/?tab=linux#enable-ip-forwarding) for how to enable IP fowarding. + ## On the control server ```console diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 00000000..6331c54a --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,53 @@ +--- +hide: + - navigation +--- + +# Frequently Asked Questions + +## What is the design goal of headscale? + +`headscale` aims to implement a self-hosted, open source alternative to the [Tailscale](https://tailscale.com/) +control server. +`headscale`'s goal is to provide self-hosters and hobbyists with an open-source +server they can use for their projects and labs. +It implements a narrow scope, a _single_ Tailnet, suitable for a personal use, or a small +open-source organisation. + +## How can I contribute? + +Headscale is "Open Source, acknowledged contribution", this means that any +contribution will have to be discussed with the Maintainers before being submitted. + +Headscale is open to code contributions for bug fixes without discussion. + +If you find mistakes in the documentation, please also submit a fix to the documentation. + +## Why is 'acknowledged contribution' the chosen model? + +Both maintainers have full-time jobs and families, and we want to avoid burnout. We also want to avoid frustration from contributors when their PRs are not accepted. + +We are more than happy to exchange emails, or to have dedicated calls before a PR is submitted. + +## When/Why is Feature X going to be implemented? + +We don't know. We might be working on it. If you want to help, please send us a PR. + +Please be aware that there are a number of reasons why we might not accept specific contributions: + +- It is not possible to implement the feature in a way that makes sense in a self-hosted environment. +- Given that we are reverse-engineering Tailscale to satify our own curiosity, we might be interested in implementing the feature ourselves. +- You are not sending unit and integration tests with it. + +## Do you support Y method of deploying Headscale? + +We currently support deploying `headscale` using our binaries and the DEB packages. Both can be found in the +[GitHub releases page](https://github.com/juanfont/headscale/releases). + +In addition to that, there are semi-official RPM packages by the Fedora infra team https://copr.fedorainfracloud.org/coprs/jonathanspw/headscale/ + +For convenience, we also build Docker images with `headscale`. But **please be aware that we don't officially support deploying `headscale` using Docker**. We have a [Discord channel](https://discord.com/channels/896711691637780480/1070619770942148618) where you can ask for Docker-specific help to the community. + +## Why is my reverse proxy not working with Headscale? + +We don't know. We don't use reverse proxies with `headscale` ourselves, so we don't have any experience with them. We have [community documentation](https://headscale.net/reverse-proxy/) on how to configure various reverse proxies, and a dedicated [Discord channel](https://discord.com/channels/896711691637780480/1070619818346164324) where you can ask for help to the community. diff --git a/docs/index.md b/docs/index.md index 2130f7a5..d13339d8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,9 +4,40 @@ hide: - toc --- -# headscale documentation +# headscale -This site contains the official and community contributed documentation for `headscale`. +`headscale` is an open source, self-hosted implementation of the Tailscale control server. -If you are having trouble with following the documentation or get unexpected results, -please ask on [Discord](https://discord.gg/c84AZQhmpx) instead of opening an Issue. +This page contains the documentation for the latest version of headscale. Please also check our [FAQ](/faq/). + +Join our [Discord](https://discord.gg/c84AZQhmpx) server for a chat and community support. + +## Design goal + +Headscale aims to implement a self-hosted, open source alternative to the Tailscale +control server. +Headscale's goal is to provide self-hosters and hobbyists with an open-source +server they can use for their projects and labs. +It implements a narrower scope, a single Tailnet, suitable for a personal use, or a small +open-source organisation. + +## Supporting headscale + +If you like `headscale` and find it useful, there is a sponsorship and donation +buttons available in the repo. + +## Contributing + +Headscale is "Open Source, acknowledged contribution", this means that any +contribution will have to be discussed with the Maintainers before being submitted. + +This model has been chosen to reduce the risk of burnout by limiting the +maintenance overhead of reviewing and validating third-party code. + +Headscale is open to code contributions for bug fixes without discussion. + +If you find mistakes in the documentation, please submit a fix to the documentation. + +## About + +`headscale` is maintained by [Kristoffer Dalby](https://kradalby.no/) and [Juan Font](https://font.eu). diff --git a/docs/web-ui.md b/docs/web-ui.md new file mode 100644 index 00000000..d018666e --- /dev/null +++ b/docs/web-ui.md @@ -0,0 +1,14 @@ +# Headscale web interface + +!!! warning "Community contributions" + + This page contains community contributions. The projects listed here are not + maintained by the Headscale authors and are written by community members. + +| Name | Repository Link | Description | Status | +| --------------- | ------------------------------------------------------- | ------------------------------------------------------------------------- | ------ | +| headscale-webui | [Github](https://github.com/ifargle/headscale-webui) | A simple Headscale web UI for small-scale deployments. | Alpha | +| headscale-ui | [Github](https://github.com/gurucomputing/headscale-ui) | A web frontend for the headscale Tailscale-compatible coordination server | Alpha | +| HeadscaleUi | [GitHub](https://github.com/simcu/headscale-ui) | A static headscale admin ui, no backend enviroment required | Alpha | + +You can ask for support on our dedicated [Discord channel](https://discord.com/channels/896711691637780480/1105842846386356294). diff --git a/flake.nix b/flake.nix index 0d4eae54..0363238b 100644 --- a/flake.nix +++ b/flake.nix @@ -36,7 +36,7 @@ # When updating go.mod or go.sum, a new sha will need to be calculated, # update this if you have a mismatch after doing a change to thos files. - vendorSha256 = "sha256-cmDNYWYTgQp6CPgpL4d3TbkpAe7rhNAF+o8njJsgL7E="; + vendorSha256 = "sha256-IOkbbFtE6+tNKnglE/8ZuNxhPSnloqM2sLgTvagMmnc="; ldflags = [ "-s" "-w" "-X github.com/juanfont/headscale/cmd/headscale/cli.Version=v${version}" ]; }; diff --git a/gen/go/headscale/v1/apikey.pb.go b/gen/go/headscale/v1/apikey.pb.go index 4e29272c..cebca90d 100644 --- a/gen/go/headscale/v1/apikey.pb.go +++ b/gen/go/headscale/v1/apikey.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.29.1 // protoc (unknown) // source: headscale/v1/apikey.proto diff --git a/gen/go/headscale/v1/device.pb.go b/gen/go/headscale/v1/device.pb.go index dc8f4980..6d626c9d 100644 --- a/gen/go/headscale/v1/device.pb.go +++ b/gen/go/headscale/v1/device.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.29.1 // protoc (unknown) // source: headscale/v1/device.proto diff --git a/gen/go/headscale/v1/headscale.pb.go b/gen/go/headscale/v1/headscale.pb.go index 33836402..d29ab04b 100644 --- a/gen/go/headscale/v1/headscale.pb.go +++ b/gen/go/headscale/v1/headscale.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.29.1 // protoc (unknown) // source: headscale/v1/headscale.proto diff --git a/gen/go/headscale/v1/headscale_grpc.pb.go b/gen/go/headscale/v1/headscale_grpc.pb.go index c1839ed7..b5f67c7f 100644 --- a/gen/go/headscale/v1/headscale_grpc.pb.go +++ b/gen/go/headscale/v1/headscale_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 +// - protoc-gen-go-grpc v1.3.0 // - protoc (unknown) // source: headscale/v1/headscale.proto @@ -18,6 +18,34 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + HeadscaleService_GetUser_FullMethodName = "/headscale.v1.HeadscaleService/GetUser" + HeadscaleService_CreateUser_FullMethodName = "/headscale.v1.HeadscaleService/CreateUser" + HeadscaleService_RenameUser_FullMethodName = "/headscale.v1.HeadscaleService/RenameUser" + HeadscaleService_DeleteUser_FullMethodName = "/headscale.v1.HeadscaleService/DeleteUser" + HeadscaleService_ListUsers_FullMethodName = "/headscale.v1.HeadscaleService/ListUsers" + HeadscaleService_CreatePreAuthKey_FullMethodName = "/headscale.v1.HeadscaleService/CreatePreAuthKey" + HeadscaleService_ExpirePreAuthKey_FullMethodName = "/headscale.v1.HeadscaleService/ExpirePreAuthKey" + HeadscaleService_ListPreAuthKeys_FullMethodName = "/headscale.v1.HeadscaleService/ListPreAuthKeys" + HeadscaleService_DebugCreateMachine_FullMethodName = "/headscale.v1.HeadscaleService/DebugCreateMachine" + HeadscaleService_GetMachine_FullMethodName = "/headscale.v1.HeadscaleService/GetMachine" + HeadscaleService_SetTags_FullMethodName = "/headscale.v1.HeadscaleService/SetTags" + HeadscaleService_RegisterMachine_FullMethodName = "/headscale.v1.HeadscaleService/RegisterMachine" + HeadscaleService_DeleteMachine_FullMethodName = "/headscale.v1.HeadscaleService/DeleteMachine" + HeadscaleService_ExpireMachine_FullMethodName = "/headscale.v1.HeadscaleService/ExpireMachine" + HeadscaleService_RenameMachine_FullMethodName = "/headscale.v1.HeadscaleService/RenameMachine" + HeadscaleService_ListMachines_FullMethodName = "/headscale.v1.HeadscaleService/ListMachines" + HeadscaleService_MoveMachine_FullMethodName = "/headscale.v1.HeadscaleService/MoveMachine" + HeadscaleService_GetRoutes_FullMethodName = "/headscale.v1.HeadscaleService/GetRoutes" + HeadscaleService_EnableRoute_FullMethodName = "/headscale.v1.HeadscaleService/EnableRoute" + HeadscaleService_DisableRoute_FullMethodName = "/headscale.v1.HeadscaleService/DisableRoute" + HeadscaleService_GetMachineRoutes_FullMethodName = "/headscale.v1.HeadscaleService/GetMachineRoutes" + HeadscaleService_DeleteRoute_FullMethodName = "/headscale.v1.HeadscaleService/DeleteRoute" + HeadscaleService_CreateApiKey_FullMethodName = "/headscale.v1.HeadscaleService/CreateApiKey" + HeadscaleService_ExpireApiKey_FullMethodName = "/headscale.v1.HeadscaleService/ExpireApiKey" + HeadscaleService_ListApiKeys_FullMethodName = "/headscale.v1.HeadscaleService/ListApiKeys" +) + // 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. @@ -64,7 +92,7 @@ func NewHeadscaleServiceClient(cc grpc.ClientConnInterface) HeadscaleServiceClie func (c *headscaleServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*GetUserResponse, error) { out := new(GetUserResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetUser", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_GetUser_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -73,7 +101,7 @@ func (c *headscaleServiceClient) GetUser(ctx context.Context, in *GetUserRequest func (c *headscaleServiceClient) CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*CreateUserResponse, error) { out := new(CreateUserResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/CreateUser", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_CreateUser_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -82,7 +110,7 @@ func (c *headscaleServiceClient) CreateUser(ctx context.Context, in *CreateUserR func (c *headscaleServiceClient) RenameUser(ctx context.Context, in *RenameUserRequest, opts ...grpc.CallOption) (*RenameUserResponse, error) { out := new(RenameUserResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/RenameUser", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_RenameUser_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -91,7 +119,7 @@ func (c *headscaleServiceClient) RenameUser(ctx context.Context, in *RenameUserR func (c *headscaleServiceClient) DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*DeleteUserResponse, error) { out := new(DeleteUserResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DeleteUser", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_DeleteUser_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -100,7 +128,7 @@ func (c *headscaleServiceClient) DeleteUser(ctx context.Context, in *DeleteUserR func (c *headscaleServiceClient) ListUsers(ctx context.Context, in *ListUsersRequest, opts ...grpc.CallOption) (*ListUsersResponse, error) { out := new(ListUsersResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListUsers", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_ListUsers_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -109,7 +137,7 @@ func (c *headscaleServiceClient) ListUsers(ctx context.Context, in *ListUsersReq 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...) + err := c.cc.Invoke(ctx, HeadscaleService_CreatePreAuthKey_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -118,7 +146,7 @@ func (c *headscaleServiceClient) CreatePreAuthKey(ctx context.Context, in *Creat 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...) + err := c.cc.Invoke(ctx, HeadscaleService_ExpirePreAuthKey_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -127,7 +155,7 @@ func (c *headscaleServiceClient) ExpirePreAuthKey(ctx context.Context, in *Expir 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...) + err := c.cc.Invoke(ctx, HeadscaleService_ListPreAuthKeys_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -136,7 +164,7 @@ func (c *headscaleServiceClient) ListPreAuthKeys(ctx context.Context, in *ListPr 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...) + err := c.cc.Invoke(ctx, HeadscaleService_DebugCreateMachine_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -145,7 +173,7 @@ func (c *headscaleServiceClient) DebugCreateMachine(ctx context.Context, in *Deb 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...) + err := c.cc.Invoke(ctx, HeadscaleService_GetMachine_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -154,7 +182,7 @@ func (c *headscaleServiceClient) GetMachine(ctx context.Context, in *GetMachineR func (c *headscaleServiceClient) SetTags(ctx context.Context, in *SetTagsRequest, opts ...grpc.CallOption) (*SetTagsResponse, error) { out := new(SetTagsResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/SetTags", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_SetTags_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -163,7 +191,7 @@ func (c *headscaleServiceClient) SetTags(ctx context.Context, in *SetTagsRequest 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...) + err := c.cc.Invoke(ctx, HeadscaleService_RegisterMachine_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -172,7 +200,7 @@ func (c *headscaleServiceClient) RegisterMachine(ctx context.Context, in *Regist 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...) + err := c.cc.Invoke(ctx, HeadscaleService_DeleteMachine_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -181,7 +209,7 @@ func (c *headscaleServiceClient) DeleteMachine(ctx context.Context, in *DeleteMa 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...) + err := c.cc.Invoke(ctx, HeadscaleService_ExpireMachine_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -190,7 +218,7 @@ func (c *headscaleServiceClient) ExpireMachine(ctx context.Context, in *ExpireMa func (c *headscaleServiceClient) RenameMachine(ctx context.Context, in *RenameMachineRequest, opts ...grpc.CallOption) (*RenameMachineResponse, error) { out := new(RenameMachineResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/RenameMachine", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_RenameMachine_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -199,7 +227,7 @@ func (c *headscaleServiceClient) RenameMachine(ctx context.Context, in *RenameMa 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...) + err := c.cc.Invoke(ctx, HeadscaleService_ListMachines_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -208,7 +236,7 @@ func (c *headscaleServiceClient) ListMachines(ctx context.Context, in *ListMachi func (c *headscaleServiceClient) MoveMachine(ctx context.Context, in *MoveMachineRequest, opts ...grpc.CallOption) (*MoveMachineResponse, error) { out := new(MoveMachineResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/MoveMachine", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_MoveMachine_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -217,7 +245,7 @@ func (c *headscaleServiceClient) MoveMachine(ctx context.Context, in *MoveMachin func (c *headscaleServiceClient) GetRoutes(ctx context.Context, in *GetRoutesRequest, opts ...grpc.CallOption) (*GetRoutesResponse, error) { out := new(GetRoutesResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetRoutes", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_GetRoutes_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -226,7 +254,7 @@ func (c *headscaleServiceClient) GetRoutes(ctx context.Context, in *GetRoutesReq func (c *headscaleServiceClient) EnableRoute(ctx context.Context, in *EnableRouteRequest, opts ...grpc.CallOption) (*EnableRouteResponse, error) { out := new(EnableRouteResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/EnableRoute", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_EnableRoute_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -235,7 +263,7 @@ func (c *headscaleServiceClient) EnableRoute(ctx context.Context, in *EnableRout func (c *headscaleServiceClient) DisableRoute(ctx context.Context, in *DisableRouteRequest, opts ...grpc.CallOption) (*DisableRouteResponse, error) { out := new(DisableRouteResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DisableRoute", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_DisableRoute_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -244,7 +272,7 @@ func (c *headscaleServiceClient) DisableRoute(ctx context.Context, in *DisableRo func (c *headscaleServiceClient) GetMachineRoutes(ctx context.Context, in *GetMachineRoutesRequest, opts ...grpc.CallOption) (*GetMachineRoutesResponse, error) { out := new(GetMachineRoutesResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/GetMachineRoutes", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_GetMachineRoutes_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -253,7 +281,7 @@ func (c *headscaleServiceClient) GetMachineRoutes(ctx context.Context, in *GetMa func (c *headscaleServiceClient) DeleteRoute(ctx context.Context, in *DeleteRouteRequest, opts ...grpc.CallOption) (*DeleteRouteResponse, error) { out := new(DeleteRouteResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/DeleteRoute", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_DeleteRoute_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -262,7 +290,7 @@ func (c *headscaleServiceClient) DeleteRoute(ctx context.Context, in *DeleteRout func (c *headscaleServiceClient) CreateApiKey(ctx context.Context, in *CreateApiKeyRequest, opts ...grpc.CallOption) (*CreateApiKeyResponse, error) { out := new(CreateApiKeyResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/CreateApiKey", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_CreateApiKey_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -271,7 +299,7 @@ func (c *headscaleServiceClient) CreateApiKey(ctx context.Context, in *CreateApi func (c *headscaleServiceClient) ExpireApiKey(ctx context.Context, in *ExpireApiKeyRequest, opts ...grpc.CallOption) (*ExpireApiKeyResponse, error) { out := new(ExpireApiKeyResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ExpireApiKey", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_ExpireApiKey_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -280,7 +308,7 @@ func (c *headscaleServiceClient) ExpireApiKey(ctx context.Context, in *ExpireApi func (c *headscaleServiceClient) ListApiKeys(ctx context.Context, in *ListApiKeysRequest, opts ...grpc.CallOption) (*ListApiKeysResponse, error) { out := new(ListApiKeysResponse) - err := c.cc.Invoke(ctx, "/headscale.v1.HeadscaleService/ListApiKeys", in, out, opts...) + err := c.cc.Invoke(ctx, HeadscaleService_ListApiKeys_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -426,7 +454,7 @@ func _HeadscaleService_GetUser_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/GetUser", + FullMethod: HeadscaleService_GetUser_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).GetUser(ctx, req.(*GetUserRequest)) @@ -444,7 +472,7 @@ func _HeadscaleService_CreateUser_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/CreateUser", + FullMethod: HeadscaleService_CreateUser_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).CreateUser(ctx, req.(*CreateUserRequest)) @@ -462,7 +490,7 @@ func _HeadscaleService_RenameUser_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/RenameUser", + FullMethod: HeadscaleService_RenameUser_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).RenameUser(ctx, req.(*RenameUserRequest)) @@ -480,7 +508,7 @@ func _HeadscaleService_DeleteUser_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/DeleteUser", + FullMethod: HeadscaleService_DeleteUser_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).DeleteUser(ctx, req.(*DeleteUserRequest)) @@ -498,7 +526,7 @@ func _HeadscaleService_ListUsers_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/ListUsers", + FullMethod: HeadscaleService_ListUsers_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).ListUsers(ctx, req.(*ListUsersRequest)) @@ -516,7 +544,7 @@ func _HeadscaleService_CreatePreAuthKey_Handler(srv interface{}, ctx context.Con } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/CreatePreAuthKey", + FullMethod: HeadscaleService_CreatePreAuthKey_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).CreatePreAuthKey(ctx, req.(*CreatePreAuthKeyRequest)) @@ -534,7 +562,7 @@ func _HeadscaleService_ExpirePreAuthKey_Handler(srv interface{}, ctx context.Con } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/ExpirePreAuthKey", + FullMethod: HeadscaleService_ExpirePreAuthKey_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).ExpirePreAuthKey(ctx, req.(*ExpirePreAuthKeyRequest)) @@ -552,7 +580,7 @@ func _HeadscaleService_ListPreAuthKeys_Handler(srv interface{}, ctx context.Cont } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/ListPreAuthKeys", + FullMethod: HeadscaleService_ListPreAuthKeys_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).ListPreAuthKeys(ctx, req.(*ListPreAuthKeysRequest)) @@ -570,7 +598,7 @@ func _HeadscaleService_DebugCreateMachine_Handler(srv interface{}, ctx context.C } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/DebugCreateMachine", + FullMethod: HeadscaleService_DebugCreateMachine_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).DebugCreateMachine(ctx, req.(*DebugCreateMachineRequest)) @@ -588,7 +616,7 @@ func _HeadscaleService_GetMachine_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/GetMachine", + FullMethod: HeadscaleService_GetMachine_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).GetMachine(ctx, req.(*GetMachineRequest)) @@ -606,7 +634,7 @@ func _HeadscaleService_SetTags_Handler(srv interface{}, ctx context.Context, dec } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/SetTags", + FullMethod: HeadscaleService_SetTags_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).SetTags(ctx, req.(*SetTagsRequest)) @@ -624,7 +652,7 @@ func _HeadscaleService_RegisterMachine_Handler(srv interface{}, ctx context.Cont } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/RegisterMachine", + FullMethod: HeadscaleService_RegisterMachine_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).RegisterMachine(ctx, req.(*RegisterMachineRequest)) @@ -642,7 +670,7 @@ func _HeadscaleService_DeleteMachine_Handler(srv interface{}, ctx context.Contex } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/DeleteMachine", + FullMethod: HeadscaleService_DeleteMachine_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).DeleteMachine(ctx, req.(*DeleteMachineRequest)) @@ -660,7 +688,7 @@ func _HeadscaleService_ExpireMachine_Handler(srv interface{}, ctx context.Contex } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/ExpireMachine", + FullMethod: HeadscaleService_ExpireMachine_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).ExpireMachine(ctx, req.(*ExpireMachineRequest)) @@ -678,7 +706,7 @@ func _HeadscaleService_RenameMachine_Handler(srv interface{}, ctx context.Contex } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/RenameMachine", + FullMethod: HeadscaleService_RenameMachine_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).RenameMachine(ctx, req.(*RenameMachineRequest)) @@ -696,7 +724,7 @@ func _HeadscaleService_ListMachines_Handler(srv interface{}, ctx context.Context } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/ListMachines", + FullMethod: HeadscaleService_ListMachines_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).ListMachines(ctx, req.(*ListMachinesRequest)) @@ -714,7 +742,7 @@ func _HeadscaleService_MoveMachine_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/MoveMachine", + FullMethod: HeadscaleService_MoveMachine_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).MoveMachine(ctx, req.(*MoveMachineRequest)) @@ -732,7 +760,7 @@ func _HeadscaleService_GetRoutes_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/GetRoutes", + FullMethod: HeadscaleService_GetRoutes_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).GetRoutes(ctx, req.(*GetRoutesRequest)) @@ -750,7 +778,7 @@ func _HeadscaleService_EnableRoute_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/EnableRoute", + FullMethod: HeadscaleService_EnableRoute_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).EnableRoute(ctx, req.(*EnableRouteRequest)) @@ -768,7 +796,7 @@ func _HeadscaleService_DisableRoute_Handler(srv interface{}, ctx context.Context } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/DisableRoute", + FullMethod: HeadscaleService_DisableRoute_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).DisableRoute(ctx, req.(*DisableRouteRequest)) @@ -786,7 +814,7 @@ func _HeadscaleService_GetMachineRoutes_Handler(srv interface{}, ctx context.Con } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/GetMachineRoutes", + FullMethod: HeadscaleService_GetMachineRoutes_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).GetMachineRoutes(ctx, req.(*GetMachineRoutesRequest)) @@ -804,7 +832,7 @@ func _HeadscaleService_DeleteRoute_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/DeleteRoute", + FullMethod: HeadscaleService_DeleteRoute_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).DeleteRoute(ctx, req.(*DeleteRouteRequest)) @@ -822,7 +850,7 @@ func _HeadscaleService_CreateApiKey_Handler(srv interface{}, ctx context.Context } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/CreateApiKey", + FullMethod: HeadscaleService_CreateApiKey_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).CreateApiKey(ctx, req.(*CreateApiKeyRequest)) @@ -840,7 +868,7 @@ func _HeadscaleService_ExpireApiKey_Handler(srv interface{}, ctx context.Context } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/ExpireApiKey", + FullMethod: HeadscaleService_ExpireApiKey_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).ExpireApiKey(ctx, req.(*ExpireApiKeyRequest)) @@ -858,7 +886,7 @@ func _HeadscaleService_ListApiKeys_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/headscale.v1.HeadscaleService/ListApiKeys", + FullMethod: HeadscaleService_ListApiKeys_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(HeadscaleServiceServer).ListApiKeys(ctx, req.(*ListApiKeysRequest)) diff --git a/gen/go/headscale/v1/machine.pb.go b/gen/go/headscale/v1/machine.pb.go index 616ef2f1..c6b70dfb 100644 --- a/gen/go/headscale/v1/machine.pb.go +++ b/gen/go/headscale/v1/machine.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.29.1 // protoc (unknown) // source: headscale/v1/machine.proto diff --git a/gen/go/headscale/v1/preauthkey.pb.go b/gen/go/headscale/v1/preauthkey.pb.go index 4b03209f..0698b34b 100644 --- a/gen/go/headscale/v1/preauthkey.pb.go +++ b/gen/go/headscale/v1/preauthkey.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.29.1 // protoc (unknown) // source: headscale/v1/preauthkey.proto diff --git a/gen/go/headscale/v1/routes.pb.go b/gen/go/headscale/v1/routes.pb.go index 4da8250a..154f78bb 100644 --- a/gen/go/headscale/v1/routes.pb.go +++ b/gen/go/headscale/v1/routes.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.29.1 // protoc (unknown) // source: headscale/v1/routes.proto diff --git a/gen/go/headscale/v1/user.pb.go b/gen/go/headscale/v1/user.pb.go index 905afc86..46c3740d 100644 --- a/gen/go/headscale/v1/user.pb.go +++ b/gen/go/headscale/v1/user.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 +// protoc-gen-go v1.29.1 // protoc (unknown) // source: headscale/v1/user.proto diff --git a/acls.go b/hscontrol/acls.go similarity index 97% rename from acls.go rename to hscontrol/acls.go index a771df50..449c7ffd 100644 --- a/acls.go +++ b/hscontrol/acls.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "encoding/json" @@ -59,8 +59,8 @@ const ( var featureEnableSSH = envknob.RegisterBool("HEADSCALE_EXPERIMENTAL_FEATURE_SSH") -// LoadACLPolicy loads the ACL policy from the specify path, and generates the ACL rules. -func (h *Headscale) LoadACLPolicy(path string) error { +// LoadACLPolicyFromPath loads the ACL policy from the specify path, and generates the ACL rules. +func (h *Headscale) LoadACLPolicyFromPath(path string) error { log.Debug(). Str("func", "LoadACLPolicy"). Str("path", path). @@ -72,37 +72,42 @@ func (h *Headscale) LoadACLPolicy(path string) error { } defer policyFile.Close() - var policy ACLPolicy policyBytes, err := io.ReadAll(policyFile) if err != nil { return err } + log.Debug(). + Str("path", path). + Bytes("file", policyBytes). + Msg("Loading ACLs") + switch filepath.Ext(path) { case ".yml", ".yaml": - log.Debug(). - Str("path", path). - Bytes("file", policyBytes). - Msg("Loading ACLs from YAML") + return h.LoadACLPolicyFromBytes(policyBytes, "yaml") + } - err := yaml.Unmarshal(policyBytes, &policy) + return h.LoadACLPolicyFromBytes(policyBytes, "hujson") +} + +func (h *Headscale) LoadACLPolicyFromBytes(acl []byte, format string) error { + var policy ACLPolicy + switch format { + case "yaml": + err := yaml.Unmarshal(acl, &policy) if err != nil { return err } - log.Trace(). - Interface("policy", policy). - Msg("Loaded policy from YAML") - default: - ast, err := hujson.Parse(policyBytes) + ast, err := hujson.Parse(acl) if err != nil { return err } ast.Standardize() - policyBytes = ast.Pack() - err = json.Unmarshal(policyBytes, &policy) + acl = ast.Pack() + err = json.Unmarshal(acl, &policy) if err != nil { return err } diff --git a/acls_test.go b/hscontrol/acls_test.go similarity index 86% rename from acls_test.go rename to hscontrol/acls_test.go index d6e10a73..095597f2 100644 --- a/acls_test.go +++ b/hscontrol/acls_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "errors" @@ -15,17 +15,26 @@ import ( ) func (s *Suite) TestWrongPath(c *check.C) { - err := app.LoadACLPolicy("asdfg") + err := app.LoadACLPolicyFromPath("asdfg") c.Assert(err, check.NotNil) } func (s *Suite) TestBrokenHuJson(c *check.C) { - err := app.LoadACLPolicy("./tests/acls/broken.hujson") + acl := []byte(` +{ + `) + err := app.LoadACLPolicyFromBytes(acl, "hujson") c.Assert(err, check.NotNil) } func (s *Suite) TestInvalidPolicyHuson(c *check.C) { - err := app.LoadACLPolicy("./tests/acls/invalid.hujson") + acl := []byte(` +{ + "valid_json": true, + "but_a_policy_though": false +} + `) + err := app.LoadACLPolicyFromBytes(acl, "hujson") c.Assert(err, check.NotNil) c.Assert(err, check.Equals, errEmptyPolicy) } @@ -49,12 +58,161 @@ func (s *Suite) TestParseInvalidCIDR(c *check.C) { } func (s *Suite) TestRuleInvalidGeneration(c *check.C) { - err := app.LoadACLPolicy("./tests/acls/acl_policy_invalid.hujson") + acl := []byte(` +{ + // Declare static groups of users beyond those in the identity service. + "groups": { + "group:example": [ + "user1@example.com", + "user2@example.com", + ], + }, + // Declare hostname aliases to use in place of IP addresses or subnets. + "hosts": { + "example-host-1": "100.100.100.100", + "example-host-2": "100.100.101.100/24", + }, + // Define who is allowed to use which tags. + "tagOwners": { + // Everyone in the montreal-admins or global-admins group are + // allowed to tag servers as montreal-webserver. + "tag:montreal-webserver": [ + "group:montreal-admins", + "group:global-admins", + ], + // Only a few admins are allowed to create API servers. + "tag:api-server": [ + "group:global-admins", + "example-host-1", + ], + }, + // Access control lists. + "acls": [ + // Engineering users, plus the president, can access port 22 (ssh) + // and port 3389 (remote desktop protocol) on all servers, and all + // ports on git-server or ci-server. + { + "action": "accept", + "src": [ + "group:engineering", + "president@example.com" + ], + "dst": [ + "*:22,3389", + "git-server:*", + "ci-server:*" + ], + }, + // Allow engineer users to access any port on a device tagged with + // tag:production. + { + "action": "accept", + "src": [ + "group:engineers" + ], + "dst": [ + "tag:production:*" + ], + }, + // Allow servers in the my-subnet host and 192.168.1.0/24 to access hosts + // on both networks. + { + "action": "accept", + "src": [ + "my-subnet", + "192.168.1.0/24" + ], + "dst": [ + "my-subnet:*", + "192.168.1.0/24:*" + ], + }, + // Allow every user of your network to access anything on the network. + // Comment out this section if you want to define specific ACL + // restrictions above. + { + "action": "accept", + "src": [ + "*" + ], + "dst": [ + "*:*" + ], + }, + // All users in Montreal are allowed to access the Montreal web + // servers. + { + "action": "accept", + "src": [ + "group:montreal-users" + ], + "dst": [ + "tag:montreal-webserver:80,443" + ], + }, + // Montreal web servers are allowed to make outgoing connections to + // the API servers, but only on https port 443. + // In contrast, this doesn't grant API servers the right to initiate + // any connections. + { + "action": "accept", + "src": [ + "tag:montreal-webserver" + ], + "dst": [ + "tag:api-server:443" + ], + }, + ], + // Declare tests to check functionality of ACL rules + "tests": [ + { + "src": "user1@example.com", + "accept": [ + "example-host-1:22", + "example-host-2:80" + ], + "deny": [ + "exapmle-host-2:100" + ], + }, + { + "src": "user2@example.com", + "accept": [ + "100.60.3.4:22" + ], + }, + ], +} + `) + err := app.LoadACLPolicyFromBytes(acl, "hujson") c.Assert(err, check.NotNil) } func (s *Suite) TestBasicRule(c *check.C) { - err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_1.hujson") + acl := []byte(` +{ + "hosts": { + "host-1": "100.100.100.100", + "subnet-1": "100.100.101.100/24", + }, + + "acls": [ + { + "action": "accept", + "src": [ + "subnet-1", + "192.168.1.0/24" + ], + "dst": [ + "*:22,3389", + "host-1:*", + ], + }, + ], +} + `) + err := app.LoadACLPolicyFromBytes(acl, "hujson") c.Assert(err, check.IsNil) rules, err := app.aclPolicy.generateFilterRules([]Machine{}, false) @@ -411,7 +569,27 @@ func (s *Suite) TestValidTagInvalidUser(c *check.C) { } func (s *Suite) TestPortRange(c *check.C) { - err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_range.hujson") + acl := []byte(` +{ + "hosts": { + "host-1": "100.100.100.100", + "subnet-1": "100.100.101.100/24", + }, + + "acls": [ + { + "action": "accept", + "src": [ + "subnet-1", + ], + "dst": [ + "host-1:5400-5500", + ], + }, + ], +} + `) + err := app.LoadACLPolicyFromBytes(acl, "hujson") c.Assert(err, check.IsNil) rules, err := app.aclPolicy.generateFilterRules([]Machine{}, false) @@ -425,7 +603,48 @@ func (s *Suite) TestPortRange(c *check.C) { } func (s *Suite) TestProtocolParsing(c *check.C) { - err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_protocols.hujson") + acl := []byte(` +{ + "hosts": { + "host-1": "100.100.100.100", + "subnet-1": "100.100.101.100/24", + }, + + "acls": [ + { + "Action": "accept", + "src": [ + "*", + ], + "proto": "tcp", + "dst": [ + "host-1:*", + ], + }, + { + "Action": "accept", + "src": [ + "*", + ], + "proto": "udp", + "dst": [ + "host-1:53", + ], + }, + { + "Action": "accept", + "src": [ + "*", + ], + "proto": "icmp", + "dst": [ + "host-1:*", + ], + }, + ], +} + `) + err := app.LoadACLPolicyFromBytes(acl, "hujson") c.Assert(err, check.IsNil) rules, err := app.aclPolicy.generateFilterRules([]Machine{}, false) @@ -439,7 +658,27 @@ func (s *Suite) TestProtocolParsing(c *check.C) { } func (s *Suite) TestPortWildcard(c *check.C) { - err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_wildcards.hujson") + acl := []byte(` +{ + "hosts": { + "host-1": "100.100.100.100", + "subnet-1": "100.100.101.100/24", + }, + + "acls": [ + { + "Action": "accept", + "src": [ + "*", + ], + "dst": [ + "host-1:*", + ], + }, + ], +} + `) + err := app.LoadACLPolicyFromBytes(acl, "hujson") c.Assert(err, check.IsNil) rules, err := app.aclPolicy.generateFilterRules([]Machine{}, false) @@ -455,7 +694,19 @@ func (s *Suite) TestPortWildcard(c *check.C) { } func (s *Suite) TestPortWildcardYAML(c *check.C) { - err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_wildcards.yaml") + acl := []byte(` +--- +hosts: + host-1: 100.100.100.100/32 + subnet-1: 100.100.101.100/24 +acls: + - action: accept + src: + - "*" + dst: + - host-1:* +`) + err := app.LoadACLPolicyFromBytes(acl, "yaml") c.Assert(err, check.IsNil) rules, err := app.aclPolicy.generateFilterRules([]Machine{}, false) @@ -493,9 +744,27 @@ func (s *Suite) TestPortUser(c *check.C) { } app.db.Save(&machine) - err = app.LoadACLPolicy( - "./tests/acls/acl_policy_basic_user_as_user.hujson", - ) + acl := []byte(` +{ + "hosts": { + "host-1": "100.100.100.100", + "subnet-1": "100.100.101.100/24", + }, + + "acls": [ + { + "action": "accept", + "src": [ + "testuser", + ], + "dst": [ + "host-1:*", + ], + }, + ], +} + `) + err = app.LoadACLPolicyFromBytes(acl, "hujson") c.Assert(err, check.IsNil) machines, err := app.ListMachines() @@ -538,7 +807,33 @@ func (s *Suite) TestPortGroup(c *check.C) { } app.db.Save(&machine) - err = app.LoadACLPolicy("./tests/acls/acl_policy_basic_groups.hujson") + acl := []byte(` +{ + "groups": { + "group:example": [ + "testuser", + ], + }, + + "hosts": { + "host-1": "100.100.100.100", + "subnet-1": "100.100.101.100/24", + }, + + "acls": [ + { + "action": "accept", + "src": [ + "group:example", + ], + "dst": [ + "host-1:*", + ], + }, + ], +} + `) + err = app.LoadACLPolicyFromBytes(acl, "hujson") c.Assert(err, check.IsNil) machines, err := app.ListMachines() diff --git a/acls_types.go b/hscontrol/acls_types.go similarity index 99% rename from acls_types.go rename to hscontrol/acls_types.go index b98b2d2a..0e553515 100644 --- a/acls_types.go +++ b/hscontrol/acls_types.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "encoding/json" diff --git a/api.go b/hscontrol/api.go similarity index 99% rename from api.go rename to hscontrol/api.go index 308bc281..f8b1496f 100644 --- a/api.go +++ b/hscontrol/api.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "bytes" diff --git a/api_common.go b/hscontrol/api_common.go similarity index 99% rename from api_common.go rename to hscontrol/api_common.go index 7905c29d..3dd65ac6 100644 --- a/api_common.go +++ b/hscontrol/api_common.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "time" diff --git a/api_key.go b/hscontrol/api_key.go similarity index 99% rename from api_key.go rename to hscontrol/api_key.go index ae19eb9c..6382a331 100644 --- a/api_key.go +++ b/hscontrol/api_key.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "fmt" diff --git a/api_key_test.go b/hscontrol/api_key_test.go similarity index 99% rename from api_key_test.go rename to hscontrol/api_key_test.go index 2ddbbbc0..fd4fa00d 100644 --- a/api_key_test.go +++ b/hscontrol/api_key_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "time" diff --git a/app.go b/hscontrol/app.go similarity index 98% rename from app.go rename to hscontrol/app.go index 79784ba1..b8dceba8 100644 --- a/app.go +++ b/hscontrol/app.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "context" @@ -21,6 +21,7 @@ import ( "github.com/gorilla/mux" grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/juanfont/headscale" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" "github.com/patrickmn/go-cache" zerolog "github.com/philip-bui/grpc-zerolog" @@ -507,8 +508,10 @@ func (h *Headscale) createRouter(grpcMux *runtime.ServeMux) *mux.Router { router.HandleFunc("/windows", h.WindowsConfigMessage).Methods(http.MethodGet) router.HandleFunc("/windows/tailscale.reg", h.WindowsRegConfig). Methods(http.MethodGet) - router.HandleFunc("/swagger", SwaggerUI).Methods(http.MethodGet) - router.HandleFunc("/swagger/v1/openapiv2.json", SwaggerAPIv1). + + // TODO(kristoffer): move swagger into a package + router.HandleFunc("/swagger", headscale.SwaggerUI).Methods(http.MethodGet) + router.HandleFunc("/swagger/v1/openapiv2.json", headscale.SwaggerAPIv1). Methods(http.MethodGet) if h.cfg.DERP.ServerEnabled { @@ -758,7 +761,7 @@ func (h *Headscale) Serve() error { if h.cfg.ACL.PolicyPath != "" { aclPath := AbsolutePathFromConfigPath(h.cfg.ACL.PolicyPath) - err := h.LoadACLPolicy(aclPath) + err := h.LoadACLPolicyFromPath(aclPath) if err != nil { log.Error().Err(err).Msg("Failed to reload ACL policy") } diff --git a/app_test.go b/hscontrol/app_test.go similarity index 97% rename from app_test.go rename to hscontrol/app_test.go index 5f23fd2b..7d3907d3 100644 --- a/app_test.go +++ b/hscontrol/app_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "net/netip" diff --git a/config.go b/hscontrol/config.go similarity index 95% rename from config.go rename to hscontrol/config.go index c0dd1c98..0e83a1c2 100644 --- a/config.go +++ b/hscontrol/config.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "errors" @@ -16,6 +16,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/viper" "go4.org/netipx" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/types/dnstype" ) @@ -174,7 +175,7 @@ func LoadConfig(path string, isFile bool) error { viper.SetDefault("derp.server.enabled", false) viper.SetDefault("derp.server.stun.enabled", true) - viper.SetDefault("unix_socket", "/var/run/headscale.sock") + viper.SetDefault("unix_socket", "/var/run/headscale/headscale.sock") viper.SetDefault("unix_socket_permission", "0o770") viper.SetDefault("grpc_listen_addr", ":50443") @@ -515,6 +516,29 @@ func GetHeadscaleConfig() (*Config, error) { if err != nil { panic(fmt.Errorf("failed to parse ip_prefixes[%d]: %w", i, err)) } + + if prefix.Addr().Is4() { + builder := netipx.IPSetBuilder{} + builder.AddPrefix(tsaddr.CGNATRange()) + ipSet, _ := builder.IPSet() + if !ipSet.ContainsPrefix(prefix) { + log.Warn(). + Msgf("Prefix %s is not in the %s range. This is an unsupported configuration.", + prefixInConfig, tsaddr.CGNATRange()) + } + } + + if prefix.Addr().Is6() { + builder := netipx.IPSetBuilder{} + builder.AddPrefix(tsaddr.TailscaleULARange()) + ipSet, _ := builder.IPSet() + if !ipSet.ContainsPrefix(prefix) { + log.Warn(). + Msgf("Prefix %s is not in the %s range. This is an unsupported configuration.", + prefixInConfig, tsaddr.TailscaleULARange()) + } + } + parsedPrefixes = append(parsedPrefixes, prefix) } diff --git a/db.go b/hscontrol/db.go similarity index 99% rename from db.go rename to hscontrol/db.go index fb5c5c35..14df4b3b 100644 --- a/db.go +++ b/hscontrol/db.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "context" diff --git a/derp.go b/hscontrol/derp.go similarity index 99% rename from derp.go rename to hscontrol/derp.go index a447da78..fbc366a9 100644 --- a/derp.go +++ b/hscontrol/derp.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "context" diff --git a/derp_server.go b/hscontrol/derp_server.go similarity index 99% rename from derp_server.go rename to hscontrol/derp_server.go index c4e0d65f..9ca6eee5 100644 --- a/derp_server.go +++ b/hscontrol/derp_server.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "context" diff --git a/dns.go b/hscontrol/dns.go similarity index 99% rename from dns.go rename to hscontrol/dns.go index da1a95d0..72c5b03c 100644 --- a/dns.go +++ b/hscontrol/dns.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "fmt" diff --git a/dns_test.go b/hscontrol/dns_test.go similarity index 99% rename from dns_test.go rename to hscontrol/dns_test.go index eb96bbe9..b8257219 100644 --- a/dns_test.go +++ b/hscontrol/dns_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "fmt" diff --git a/grpcv1.go b/hscontrol/grpcv1.go similarity index 99% rename from grpcv1.go rename to hscontrol/grpcv1.go index 2be047bb..a65a3805 100644 --- a/grpcv1.go +++ b/hscontrol/grpcv1.go @@ -1,5 +1,5 @@ // nolint -package headscale +package hscontrol import ( "context" diff --git a/grpcv1_test.go b/hscontrol/grpcv1_test.go similarity index 97% rename from grpcv1_test.go rename to hscontrol/grpcv1_test.go index e48ae1ef..1d87bfe0 100644 --- a/grpcv1_test.go +++ b/hscontrol/grpcv1_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import "testing" diff --git a/handler_legacy.go b/hscontrol/handler_legacy.go similarity index 94% rename from handler_legacy.go rename to hscontrol/handler_legacy.go index 8911d430..bb94b1e5 100644 --- a/handler_legacy.go +++ b/hscontrol/handler_legacy.go @@ -1,6 +1,6 @@ //go:build ts2019 -package headscale +package hscontrol import ( "net/http" diff --git a/handler_placeholder.go b/hscontrol/handler_placeholder.go similarity index 86% rename from handler_placeholder.go rename to hscontrol/handler_placeholder.go index 25fe9c65..73d17c49 100644 --- a/handler_placeholder.go +++ b/hscontrol/handler_placeholder.go @@ -1,6 +1,6 @@ //go:build !ts2019 -package headscale +package hscontrol import "github.com/gorilla/mux" diff --git a/machine.go b/hscontrol/machine.go similarity index 99% rename from machine.go rename to hscontrol/machine.go index 9b775a47..9f04d8ce 100644 --- a/machine.go +++ b/hscontrol/machine.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "database/sql/driver" diff --git a/machine_test.go b/hscontrol/machine_test.go similarity index 98% rename from machine_test.go rename to hscontrol/machine_test.go index 7fc641af..3f11da4b 100644 --- a/machine_test.go +++ b/hscontrol/machine_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "fmt" @@ -1212,7 +1212,31 @@ func TestHeadscale_generateGivenName(t *testing.T) { } func (s *Suite) TestAutoApproveRoutes(c *check.C) { - err := app.LoadACLPolicy("./tests/acls/acl_policy_autoapprovers.hujson") + acl := []byte(` +{ + "tagOwners": { + "tag:exit": ["test"], + }, + + "groups": { + "group:test": ["test"] + }, + + "acls": [ + {"action": "accept", "users": ["*"], "ports": ["*:*"]}, + ], + + "autoApprovers": { + "exitNode": ["tag:exit"], + "routes": { + "10.10.0.0/16": ["group:test"], + "10.11.0.0/16": ["test"], + } + } +} + `) + + err := app.LoadACLPolicyFromBytes(acl, "hujson") c.Assert(err, check.IsNil) user, err := app.CreateUser("test") diff --git a/matcher.go b/hscontrol/matcher.go similarity index 99% rename from matcher.go rename to hscontrol/matcher.go index 1a186c4c..3b4670e8 100644 --- a/matcher.go +++ b/hscontrol/matcher.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "fmt" diff --git a/matcher_test.go b/hscontrol/matcher_test.go similarity index 99% rename from matcher_test.go rename to hscontrol/matcher_test.go index 03b585c4..fb0e9b07 100644 --- a/matcher_test.go +++ b/hscontrol/matcher_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "net/netip" diff --git a/metrics.go b/hscontrol/metrics.go similarity index 98% rename from metrics.go rename to hscontrol/metrics.go index f1f86909..087ce302 100644 --- a/metrics.go +++ b/hscontrol/metrics.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "github.com/prometheus/client_golang/prometheus" diff --git a/noise.go b/hscontrol/noise.go similarity index 99% rename from noise.go rename to hscontrol/noise.go index 9cc489ae..f938dfe5 100644 --- a/noise.go +++ b/hscontrol/noise.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "encoding/binary" diff --git a/oidc.go b/hscontrol/oidc.go similarity index 99% rename from oidc.go rename to hscontrol/oidc.go index 5aa3ba62..332ce099 100644 --- a/oidc.go +++ b/hscontrol/oidc.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "bytes" diff --git a/platform_config.go b/hscontrol/platform_config.go similarity index 99% rename from platform_config.go rename to hscontrol/platform_config.go index b23133e7..0404f546 100644 --- a/platform_config.go +++ b/hscontrol/platform_config.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "bytes" diff --git a/preauth_keys.go b/hscontrol/preauth_keys.go similarity index 99% rename from preauth_keys.go rename to hscontrol/preauth_keys.go index 3222c71d..6cff90b0 100644 --- a/preauth_keys.go +++ b/hscontrol/preauth_keys.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "crypto/rand" diff --git a/preauth_keys_test.go b/hscontrol/preauth_keys_test.go similarity index 99% rename from preauth_keys_test.go rename to hscontrol/preauth_keys_test.go index 697144fe..bd383cfd 100644 --- a/preauth_keys_test.go +++ b/hscontrol/preauth_keys_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "time" diff --git a/protocol_common.go b/hscontrol/protocol_common.go similarity index 99% rename from protocol_common.go rename to hscontrol/protocol_common.go index 2a30046f..97da464b 100644 --- a/protocol_common.go +++ b/hscontrol/protocol_common.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "encoding/json" diff --git a/protocol_common_poll.go b/hscontrol/protocol_common_poll.go similarity index 99% rename from protocol_common_poll.go rename to hscontrol/protocol_common_poll.go index 09df6309..f267c999 100644 --- a/protocol_common_poll.go +++ b/hscontrol/protocol_common_poll.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "context" diff --git a/protocol_common_utils.go b/hscontrol/protocol_common_utils.go similarity index 99% rename from protocol_common_utils.go rename to hscontrol/protocol_common_utils.go index 96c236d0..e05b04a2 100644 --- a/protocol_common_utils.go +++ b/hscontrol/protocol_common_utils.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "encoding/binary" diff --git a/protocol_legacy.go b/hscontrol/protocol_legacy.go similarity index 98% rename from protocol_legacy.go rename to hscontrol/protocol_legacy.go index 99f68e5e..67128286 100644 --- a/protocol_legacy.go +++ b/hscontrol/protocol_legacy.go @@ -1,6 +1,6 @@ //go:build ts2019 -package headscale +package hscontrol import ( "io" diff --git a/protocol_legacy_poll.go b/hscontrol/protocol_legacy_poll.go similarity index 99% rename from protocol_legacy_poll.go rename to hscontrol/protocol_legacy_poll.go index a8d9343a..0121bf3f 100644 --- a/protocol_legacy_poll.go +++ b/hscontrol/protocol_legacy_poll.go @@ -1,6 +1,6 @@ //go:build ts2019 -package headscale +package hscontrol import ( "errors" diff --git a/protocol_noise.go b/hscontrol/protocol_noise.go similarity index 98% rename from protocol_noise.go rename to hscontrol/protocol_noise.go index eb18a474..dfbad73b 100644 --- a/protocol_noise.go +++ b/hscontrol/protocol_noise.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "encoding/json" diff --git a/protocol_noise_poll.go b/hscontrol/protocol_noise_poll.go similarity index 99% rename from protocol_noise_poll.go rename to hscontrol/protocol_noise_poll.go index d5e9a8a9..38f2b1c7 100644 --- a/protocol_noise_poll.go +++ b/hscontrol/protocol_noise_poll.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "encoding/json" diff --git a/routes.go b/hscontrol/routes.go similarity index 87% rename from routes.go rename to hscontrol/routes.go index bab35ea8..89f9a694 100644 --- a/routes.go +++ b/hscontrol/routes.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "errors" @@ -106,13 +106,36 @@ func (h *Headscale) DisableRoute(id uint64) error { return err } - route.Enabled = false - route.IsPrimary = false - err = h.db.Save(route).Error + // Tailscale requires both IPv4 and IPv6 exit routes to + // be enabled at the same time, as per + // https://github.com/juanfont/headscale/issues/804#issuecomment-1399314002 + if !route.isExitRoute() { + route.Enabled = false + route.IsPrimary = false + err = h.db.Save(route).Error + if err != nil { + return err + } + + return h.handlePrimarySubnetFailover() + } + + routes, err := h.GetMachineRoutes(&route.Machine) if err != nil { return err } + for i := range routes { + if routes[i].isExitRoute() { + routes[i].Enabled = false + routes[i].IsPrimary = false + err = h.db.Save(&routes[i]).Error + if err != nil { + return err + } + } + } + return h.handlePrimarySubnetFailover() } @@ -122,7 +145,30 @@ func (h *Headscale) DeleteRoute(id uint64) error { return err } - if err := h.db.Unscoped().Delete(&route).Error; err != nil { + // Tailscale requires both IPv4 and IPv6 exit routes to + // be enabled at the same time, as per + // https://github.com/juanfont/headscale/issues/804#issuecomment-1399314002 + if !route.isExitRoute() { + if err := h.db.Unscoped().Delete(&route).Error; err != nil { + return err + } + + return h.handlePrimarySubnetFailover() + } + + routes, err := h.GetMachineRoutes(&route.Machine) + if err != nil { + return err + } + + routesToDelete := []Route{} + for _, r := range routes { + if r.isExitRoute() { + routesToDelete = append(routesToDelete, r) + } + } + + if err := h.db.Unscoped().Delete(&routesToDelete).Error; err != nil { return err } diff --git a/routes_test.go b/hscontrol/routes_test.go similarity index 93% rename from routes_test.go rename to hscontrol/routes_test.go index b67b3ee9..1e5e2bbf 100644 --- a/routes_test.go +++ b/hscontrol/routes_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "net/netip" @@ -457,6 +457,37 @@ func (s *Suite) TestAllowedIPRoutes(c *check.C) { c.Assert(foundExitNodeV4, check.Equals, true) c.Assert(foundExitNodeV6, check.Equals, true) + + // Now we disable only one of the exit routes + // and we see if both are disabled + var exitRouteV4 Route + for _, route := range routes { + if route.isExitRoute() && netip.Prefix(route.Prefix) == prefixExitNodeV4 { + exitRouteV4 = route + + break + } + } + + err = app.DisableRoute(uint64(exitRouteV4.ID)) + c.Assert(err, check.IsNil) + + enabledRoutes1, err = app.GetEnabledRoutes(&machine1) + c.Assert(err, check.IsNil) + c.Assert(len(enabledRoutes1), check.Equals, 1) + + // and now we delete only one of the exit routes + // and we check if both are deleted + routes, err = app.GetMachineRoutes(&machine1) + c.Assert(err, check.IsNil) + c.Assert(len(routes), check.Equals, 4) + + err = app.DeleteRoute(uint64(exitRouteV4.ID)) + c.Assert(err, check.IsNil) + + routes, err = app.GetMachineRoutes(&machine1) + c.Assert(err, check.IsNil) + c.Assert(len(routes), check.Equals, 2) } func (s *Suite) TestDeleteRoutes(c *check.C) { diff --git a/templates/apple.html b/hscontrol/templates/apple.html similarity index 100% rename from templates/apple.html rename to hscontrol/templates/apple.html diff --git a/templates/windows.html b/hscontrol/templates/windows.html similarity index 100% rename from templates/windows.html rename to hscontrol/templates/windows.html diff --git a/users.go b/hscontrol/users.go similarity index 99% rename from users.go rename to hscontrol/users.go index c32213aa..8782a890 100644 --- a/users.go +++ b/hscontrol/users.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "errors" diff --git a/users_test.go b/hscontrol/users_test.go similarity index 99% rename from users_test.go rename to hscontrol/users_test.go index 144c3337..12aa9880 100644 --- a/users_test.go +++ b/hscontrol/users_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "net/netip" diff --git a/utils.go b/hscontrol/utils.go similarity index 99% rename from utils.go rename to hscontrol/utils.go index 8bdb2b3f..9cfbf0ca 100644 --- a/utils.go +++ b/hscontrol/utils.go @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package headscale +package hscontrol import ( "context" diff --git a/utils_test.go b/hscontrol/utils_test.go similarity index 99% rename from utils_test.go rename to hscontrol/utils_test.go index fd1d25e7..436df8ac 100644 --- a/utils_test.go +++ b/hscontrol/utils_test.go @@ -1,4 +1,4 @@ -package headscale +package hscontrol import ( "net/netip" diff --git a/integration/acl_test.go b/integration/acl_test.go index 987f1699..e85e28cd 100644 --- a/integration/acl_test.go +++ b/integration/acl_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/juanfont/headscale" + "github.com/juanfont/headscale/hscontrol" "github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/tsic" "github.com/stretchr/testify/assert" @@ -45,7 +45,7 @@ var veryLargeDestination = []string{ "208.0.0.0/4:*", } -func aclScenario(t *testing.T, policy *headscale.ACLPolicy, clientsPerUser int) *Scenario { +func aclScenario(t *testing.T, policy *hscontrol.ACLPolicy, clientsPerUser int) *Scenario { t.Helper() scenario, err := NewScenario() assert.NoError(t, err) @@ -92,7 +92,7 @@ func TestACLHostsInNetMapTable(t *testing.T) { // they can access minus one (them self). tests := map[string]struct { users map[string]int - policy headscale.ACLPolicy + policy hscontrol.ACLPolicy want map[string]int }{ // Test that when we have no ACL, each client netmap has @@ -102,8 +102,8 @@ func TestACLHostsInNetMapTable(t *testing.T) { "user1": 2, "user2": 2, }, - policy: headscale.ACLPolicy{ - ACLs: []headscale.ACL{ + policy: hscontrol.ACLPolicy{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"*"}, @@ -123,8 +123,8 @@ func TestACLHostsInNetMapTable(t *testing.T) { "user1": 2, "user2": 2, }, - policy: headscale.ACLPolicy{ - ACLs: []headscale.ACL{ + policy: hscontrol.ACLPolicy{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"user1"}, @@ -149,8 +149,8 @@ func TestACLHostsInNetMapTable(t *testing.T) { "user1": 2, "user2": 2, }, - policy: headscale.ACLPolicy{ - ACLs: []headscale.ACL{ + policy: hscontrol.ACLPolicy{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"user1"}, @@ -186,8 +186,8 @@ func TestACLHostsInNetMapTable(t *testing.T) { "user1": 2, "user2": 2, }, - policy: headscale.ACLPolicy{ - ACLs: []headscale.ACL{ + policy: hscontrol.ACLPolicy{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"user1"}, @@ -214,8 +214,8 @@ func TestACLHostsInNetMapTable(t *testing.T) { "user1": 2, "user2": 2, }, - policy: headscale.ACLPolicy{ - ACLs: []headscale.ACL{ + policy: hscontrol.ACLPolicy{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"user1"}, @@ -282,8 +282,8 @@ func TestACLAllowUser80Dst(t *testing.T) { IntegrationSkip(t) scenario := aclScenario(t, - &headscale.ACLPolicy{ - ACLs: []headscale.ACL{ + &hscontrol.ACLPolicy{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"user1"}, @@ -338,11 +338,11 @@ func TestACLDenyAllPort80(t *testing.T) { IntegrationSkip(t) scenario := aclScenario(t, - &headscale.ACLPolicy{ + &hscontrol.ACLPolicy{ Groups: map[string][]string{ "group:integration-acl-test": {"user1", "user2"}, }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"group:integration-acl-test"}, @@ -387,8 +387,8 @@ func TestACLAllowUserDst(t *testing.T) { IntegrationSkip(t) scenario := aclScenario(t, - &headscale.ACLPolicy{ - ACLs: []headscale.ACL{ + &hscontrol.ACLPolicy{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"user1"}, @@ -445,8 +445,8 @@ func TestACLAllowStarDst(t *testing.T) { IntegrationSkip(t) scenario := aclScenario(t, - &headscale.ACLPolicy{ - ACLs: []headscale.ACL{ + &hscontrol.ACLPolicy{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"user1"}, @@ -504,11 +504,11 @@ func TestACLNamedHostsCanReachBySubnet(t *testing.T) { IntegrationSkip(t) scenario := aclScenario(t, - &headscale.ACLPolicy{ - Hosts: headscale.Hosts{ + &hscontrol.ACLPolicy{ + Hosts: hscontrol.Hosts{ "all": netip.MustParsePrefix("100.64.0.0/24"), }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ // Everyone can curl test3 { Action: "accept", @@ -603,16 +603,16 @@ func TestACLNamedHostsCanReach(t *testing.T) { IntegrationSkip(t) tests := map[string]struct { - policy headscale.ACLPolicy + policy hscontrol.ACLPolicy }{ "ipv4": { - policy: headscale.ACLPolicy{ - Hosts: headscale.Hosts{ + policy: hscontrol.ACLPolicy{ + Hosts: hscontrol.Hosts{ "test1": netip.MustParsePrefix("100.64.0.1/32"), "test2": netip.MustParsePrefix("100.64.0.2/32"), "test3": netip.MustParsePrefix("100.64.0.3/32"), }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ // Everyone can curl test3 { Action: "accept", @@ -629,13 +629,13 @@ func TestACLNamedHostsCanReach(t *testing.T) { }, }, "ipv6": { - policy: headscale.ACLPolicy{ - Hosts: headscale.Hosts{ + policy: hscontrol.ACLPolicy{ + Hosts: hscontrol.Hosts{ "test1": netip.MustParsePrefix("fd7a:115c:a1e0::1/128"), "test2": netip.MustParsePrefix("fd7a:115c:a1e0::2/128"), "test3": netip.MustParsePrefix("fd7a:115c:a1e0::3/128"), }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ // Everyone can curl test3 { Action: "accept", @@ -854,11 +854,11 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { IntegrationSkip(t) tests := map[string]struct { - policy headscale.ACLPolicy + policy hscontrol.ACLPolicy }{ "ipv4": { - policy: headscale.ACLPolicy{ - ACLs: []headscale.ACL{ + policy: hscontrol.ACLPolicy{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"100.64.0.1"}, @@ -868,8 +868,8 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { }, }, "ipv6": { - policy: headscale.ACLPolicy{ - ACLs: []headscale.ACL{ + policy: hscontrol.ACLPolicy{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"fd7a:115c:a1e0::1"}, @@ -879,12 +879,12 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { }, }, "hostv4cidr": { - policy: headscale.ACLPolicy{ - Hosts: headscale.Hosts{ + policy: hscontrol.ACLPolicy{ + Hosts: hscontrol.Hosts{ "test1": netip.MustParsePrefix("100.64.0.1/32"), "test2": netip.MustParsePrefix("100.64.0.2/32"), }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"test1"}, @@ -894,12 +894,12 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { }, }, "hostv6cidr": { - policy: headscale.ACLPolicy{ - Hosts: headscale.Hosts{ + policy: hscontrol.ACLPolicy{ + Hosts: hscontrol.Hosts{ "test1": netip.MustParsePrefix("fd7a:115c:a1e0::1/128"), "test2": netip.MustParsePrefix("fd7a:115c:a1e0::2/128"), }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"test1"}, @@ -909,12 +909,12 @@ func TestACLDevice1CanAccessDevice2(t *testing.T) { }, }, "group": { - policy: headscale.ACLPolicy{ + policy: hscontrol.ACLPolicy{ Groups: map[string][]string{ "group:one": {"user1"}, "group:two": {"user2"}, }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"group:one"}, diff --git a/integration/auth_oidc_test.go b/integration/auth_oidc_test.go index 3e61c197..8ad8f329 100644 --- a/integration/auth_oidc_test.go +++ b/integration/auth_oidc_test.go @@ -14,7 +14,7 @@ import ( "testing" "time" - "github.com/juanfont/headscale" + "github.com/juanfont/headscale/hscontrol" "github.com/juanfont/headscale/integration/dockertestutil" "github.com/juanfont/headscale/integration/hsic" "github.com/ory/dockertest/v3" @@ -213,14 +213,14 @@ func (s *AuthOIDCScenario) CreateHeadscaleEnv( return nil } -func (s *AuthOIDCScenario) runMockOIDC(accessTTL time.Duration) (*headscale.OIDCConfig, error) { +func (s *AuthOIDCScenario) runMockOIDC(accessTTL time.Duration) (*hscontrol.OIDCConfig, error) { port, err := dockertestutil.RandomFreeHostPort() if err != nil { log.Fatalf("could not find an open port: %s", err) } portNotation := fmt.Sprintf("%d/tcp", port) - hash, _ := headscale.GenerateRandomStringDNSSafe(hsicOIDCMockHashLength) + hash, _ := hscontrol.GenerateRandomStringDNSSafe(hsicOIDCMockHashLength) hostname := fmt.Sprintf("hs-oidcmock-%s", hash) @@ -287,7 +287,7 @@ func (s *AuthOIDCScenario) runMockOIDC(accessTTL time.Duration) (*headscale.OIDC log.Printf("headscale mock oidc is ready for tests at %s", hostEndpoint) - return &headscale.OIDCConfig{ + return &hscontrol.OIDCConfig{ Issuer: fmt.Sprintf( "http://%s/oidc", net.JoinHostPort(s.mockOIDC.GetIPInNetwork(s.network), strconv.Itoa(port)), diff --git a/integration/cli_test.go b/integration/cli_test.go index ff90fd9a..039b065a 100644 --- a/integration/cli_test.go +++ b/integration/cli_test.go @@ -532,3 +532,989 @@ func TestEnablingRoutes(t *testing.T) { } } } + +func TestApiKeyCommand(t *testing.T) { + IntegrationSkip(t) + t.Parallel() + + count := 5 + + scenario, err := NewScenario() + assert.NoError(t, err) + + spec := map[string]int{ + "user1": 0, + "user2": 0, + } + + err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) + assert.NoError(t, err) + + headscale, err := scenario.Headscale() + assert.NoError(t, err) + + keys := make([]string, count) + + for idx := 0; idx < count; idx++ { + apiResult, err := headscale.Execute( + []string{ + "headscale", + "apikeys", + "create", + "--expiration", + "24h", + "--output", + "json", + }, + ) + assert.Nil(t, err) + assert.NotEmpty(t, apiResult) + + keys[idx] = apiResult + } + + assert.Len(t, keys, 5) + + var listedAPIKeys []v1.ApiKey + err = executeAndUnmarshal(headscale, + []string{ + "headscale", + "apikeys", + "list", + "--output", + "json", + }, + &listedAPIKeys, + ) + assert.Nil(t, err) + + assert.Len(t, listedAPIKeys, 5) + + assert.Equal(t, uint64(1), listedAPIKeys[0].Id) + assert.Equal(t, uint64(2), listedAPIKeys[1].Id) + assert.Equal(t, uint64(3), listedAPIKeys[2].Id) + assert.Equal(t, uint64(4), listedAPIKeys[3].Id) + assert.Equal(t, uint64(5), listedAPIKeys[4].Id) + + assert.NotEmpty(t, listedAPIKeys[0].Prefix) + assert.NotEmpty(t, listedAPIKeys[1].Prefix) + assert.NotEmpty(t, listedAPIKeys[2].Prefix) + assert.NotEmpty(t, listedAPIKeys[3].Prefix) + assert.NotEmpty(t, listedAPIKeys[4].Prefix) + + assert.True(t, listedAPIKeys[0].Expiration.AsTime().After(time.Now())) + assert.True(t, listedAPIKeys[1].Expiration.AsTime().After(time.Now())) + assert.True(t, listedAPIKeys[2].Expiration.AsTime().After(time.Now())) + assert.True(t, listedAPIKeys[3].Expiration.AsTime().After(time.Now())) + assert.True(t, listedAPIKeys[4].Expiration.AsTime().After(time.Now())) + + assert.True( + t, + listedAPIKeys[0].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), + ) + assert.True( + t, + listedAPIKeys[1].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), + ) + assert.True( + t, + listedAPIKeys[2].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), + ) + assert.True( + t, + listedAPIKeys[3].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), + ) + assert.True( + t, + listedAPIKeys[4].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), + ) + + expiredPrefixes := make(map[string]bool) + + // Expire three keys + for idx := 0; idx < 3; idx++ { + _, err := headscale.Execute( + []string{ + "headscale", + "apikeys", + "expire", + "--prefix", + listedAPIKeys[idx].Prefix, + }, + ) + assert.Nil(t, err) + + expiredPrefixes[listedAPIKeys[idx].Prefix] = true + } + + var listedAfterExpireAPIKeys []v1.ApiKey + err = executeAndUnmarshal(headscale, + []string{ + "headscale", + "apikeys", + "list", + "--output", + "json", + }, + &listedAfterExpireAPIKeys, + ) + assert.Nil(t, err) + + for index := range listedAfterExpireAPIKeys { + if _, ok := expiredPrefixes[listedAfterExpireAPIKeys[index].Prefix]; ok { + // Expired + assert.True( + t, + listedAfterExpireAPIKeys[index].Expiration.AsTime().Before(time.Now()), + ) + } else { + // Not expired + assert.False( + t, + listedAfterExpireAPIKeys[index].Expiration.AsTime().Before(time.Now()), + ) + } + } + + err = scenario.Shutdown() + assert.NoError(t, err) +} + +func TestNodeTagCommand(t *testing.T) { + IntegrationSkip(t) + t.Parallel() + + scenario, err := NewScenario() + assert.NoError(t, err) + + spec := map[string]int{ + "user1": 0, + } + + err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) + assert.NoError(t, err) + + headscale, err := scenario.Headscale() + assert.NoError(t, err) + + machineKeys := []string{ + "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", + "nodekey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c", + } + machines := make([]*v1.Machine, len(machineKeys)) + assert.Nil(t, err) + + for index, machineKey := range machineKeys { + _, err := headscale.Execute( + []string{ + "headscale", + "debug", + "create-node", + "--name", + fmt.Sprintf("machine-%d", index+1), + "--user", + "user1", + "--key", + machineKey, + "--output", + "json", + }, + ) + assert.Nil(t, err) + + var machine v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "--user", + "user1", + "register", + "--key", + machineKey, + "--output", + "json", + }, + &machine, + ) + assert.Nil(t, err) + + machines[index] = &machine + } + assert.Len(t, machines, len(machineKeys)) + + var machine v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "tag", + "-i", "1", + "-t", "tag:test", + "--output", "json", + }, + &machine, + ) + assert.Nil(t, err) + + assert.Equal(t, []string{"tag:test"}, machine.ForcedTags) + + // try to set a wrong tag and retrieve the error + type errOutput struct { + Error string `json:"error"` + } + var errorOutput errOutput + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "tag", + "-i", "2", + "-t", "wrong-tag", + "--output", "json", + }, + &errorOutput, + ) + assert.Nil(t, err) + assert.Contains(t, errorOutput.Error, "tag must start with the string 'tag:'") + + // Test list all nodes after added seconds + resultMachines := make([]*v1.Machine, len(machineKeys)) + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--output", "json", + }, + &resultMachines, + ) + assert.Nil(t, err) + found := false + for _, machine := range resultMachines { + if machine.ForcedTags != nil { + for _, tag := range machine.ForcedTags { + if tag == "tag:test" { + found = true + } + } + } + } + assert.Equal( + t, + true, + found, + "should find a machine with the tag 'tag:test' in the list of machines", + ) + + err = scenario.Shutdown() + assert.NoError(t, err) +} + +func TestNodeCommand(t *testing.T) { + IntegrationSkip(t) + t.Parallel() + + scenario, err := NewScenario() + assert.NoError(t, err) + + spec := map[string]int{ + "machine-user": 0, + "other-user": 0, + } + + err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) + assert.NoError(t, err) + + headscale, err := scenario.Headscale() + assert.NoError(t, err) + + // Randomly generated machine keys + machineKeys := []string{ + "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", + "nodekey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c", + "nodekey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507", + "nodekey:8bc13285cee598acf76b1824a6f4490f7f2e3751b201e28aeb3b07fe81d5b4a1", + "nodekey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", + } + machines := make([]*v1.Machine, len(machineKeys)) + assert.Nil(t, err) + + for index, machineKey := range machineKeys { + _, err := headscale.Execute( + []string{ + "headscale", + "debug", + "create-node", + "--name", + fmt.Sprintf("machine-%d", index+1), + "--user", + "machine-user", + "--key", + machineKey, + "--output", + "json", + }, + ) + assert.Nil(t, err) + + var machine v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "--user", + "machine-user", + "register", + "--key", + machineKey, + "--output", + "json", + }, + &machine, + ) + assert.Nil(t, err) + + machines[index] = &machine + } + + assert.Len(t, machines, len(machineKeys)) + + // Test list all nodes after added seconds + var listAll []v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--output", + "json", + }, + &listAll, + ) + assert.Nil(t, err) + + assert.Len(t, listAll, 5) + + assert.Equal(t, uint64(1), listAll[0].Id) + assert.Equal(t, uint64(2), listAll[1].Id) + assert.Equal(t, uint64(3), listAll[2].Id) + assert.Equal(t, uint64(4), listAll[3].Id) + assert.Equal(t, uint64(5), listAll[4].Id) + + assert.Equal(t, "machine-1", listAll[0].Name) + assert.Equal(t, "machine-2", listAll[1].Name) + assert.Equal(t, "machine-3", listAll[2].Name) + assert.Equal(t, "machine-4", listAll[3].Name) + assert.Equal(t, "machine-5", listAll[4].Name) + + otherUserMachineKeys := []string{ + "nodekey:b5b444774186d4217adcec407563a1223929465ee2c68a4da13af0d0185b4f8e", + "nodekey:dc721977ac7415aafa87f7d4574cbe07c6b171834a6d37375782bdc1fb6b3584", + } + otherUserMachines := make([]*v1.Machine, len(otherUserMachineKeys)) + assert.Nil(t, err) + + for index, machineKey := range otherUserMachineKeys { + _, err := headscale.Execute( + []string{ + "headscale", + "debug", + "create-node", + "--name", + fmt.Sprintf("otherUser-machine-%d", index+1), + "--user", + "other-user", + "--key", + machineKey, + "--output", + "json", + }, + ) + assert.Nil(t, err) + + var machine v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "--user", + "other-user", + "register", + "--key", + machineKey, + "--output", + "json", + }, + &machine, + ) + assert.Nil(t, err) + + otherUserMachines[index] = &machine + } + + assert.Len(t, otherUserMachines, len(otherUserMachineKeys)) + + // Test list all nodes after added otherUser + var listAllWithotherUser []v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--output", + "json", + }, + &listAllWithotherUser, + ) + assert.Nil(t, err) + + // All nodes, machines + otherUser + assert.Len(t, listAllWithotherUser, 7) + + assert.Equal(t, uint64(6), listAllWithotherUser[5].Id) + assert.Equal(t, uint64(7), listAllWithotherUser[6].Id) + + assert.Equal(t, "otherUser-machine-1", listAllWithotherUser[5].Name) + assert.Equal(t, "otherUser-machine-2", listAllWithotherUser[6].Name) + + // Test list all nodes after added otherUser + var listOnlyotherUserMachineUser []v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--user", + "other-user", + "--output", + "json", + }, + &listOnlyotherUserMachineUser, + ) + assert.Nil(t, err) + + assert.Len(t, listOnlyotherUserMachineUser, 2) + + assert.Equal(t, uint64(6), listOnlyotherUserMachineUser[0].Id) + assert.Equal(t, uint64(7), listOnlyotherUserMachineUser[1].Id) + + assert.Equal( + t, + "otherUser-machine-1", + listOnlyotherUserMachineUser[0].Name, + ) + assert.Equal( + t, + "otherUser-machine-2", + listOnlyotherUserMachineUser[1].Name, + ) + + // Delete a machines + _, err = headscale.Execute( + []string{ + "headscale", + "nodes", + "delete", + "--identifier", + // Delete the last added machine + "4", + "--output", + "json", + "--force", + }, + ) + assert.Nil(t, err) + + // Test: list main user after machine is deleted + var listOnlyMachineUserAfterDelete []v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--user", + "machine-user", + "--output", + "json", + }, + &listOnlyMachineUserAfterDelete, + ) + assert.Nil(t, err) + + assert.Len(t, listOnlyMachineUserAfterDelete, 4) + + err = scenario.Shutdown() + assert.NoError(t, err) +} + +func TestNodeExpireCommand(t *testing.T) { + IntegrationSkip(t) + t.Parallel() + + scenario, err := NewScenario() + assert.NoError(t, err) + + spec := map[string]int{ + "machine-expire-user": 0, + } + + err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) + assert.NoError(t, err) + + headscale, err := scenario.Headscale() + assert.NoError(t, err) + + // Randomly generated machine keys + machineKeys := []string{ + "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", + "nodekey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c", + "nodekey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507", + "nodekey:8bc13285cee598acf76b1824a6f4490f7f2e3751b201e28aeb3b07fe81d5b4a1", + "nodekey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", + } + machines := make([]*v1.Machine, len(machineKeys)) + + for index, machineKey := range machineKeys { + _, err := headscale.Execute( + []string{ + "headscale", + "debug", + "create-node", + "--name", + fmt.Sprintf("machine-%d", index+1), + "--user", + "machine-expire-user", + "--key", + machineKey, + "--output", + "json", + }, + ) + assert.Nil(t, err) + + var machine v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "--user", + "machine-expire-user", + "register", + "--key", + machineKey, + "--output", + "json", + }, + &machine, + ) + assert.Nil(t, err) + + machines[index] = &machine + } + + assert.Len(t, machines, len(machineKeys)) + + var listAll []v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--output", + "json", + }, + &listAll, + ) + assert.Nil(t, err) + + assert.Len(t, listAll, 5) + + assert.True(t, listAll[0].Expiry.AsTime().IsZero()) + assert.True(t, listAll[1].Expiry.AsTime().IsZero()) + assert.True(t, listAll[2].Expiry.AsTime().IsZero()) + assert.True(t, listAll[3].Expiry.AsTime().IsZero()) + assert.True(t, listAll[4].Expiry.AsTime().IsZero()) + + for idx := 0; idx < 3; idx++ { + _, err := headscale.Execute( + []string{ + "headscale", + "nodes", + "expire", + "--identifier", + fmt.Sprintf("%d", listAll[idx].Id), + }, + ) + assert.Nil(t, err) + } + + var listAllAfterExpiry []v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--output", + "json", + }, + &listAllAfterExpiry, + ) + assert.Nil(t, err) + + assert.Len(t, listAllAfterExpiry, 5) + + assert.True(t, listAllAfterExpiry[0].Expiry.AsTime().Before(time.Now())) + assert.True(t, listAllAfterExpiry[1].Expiry.AsTime().Before(time.Now())) + assert.True(t, listAllAfterExpiry[2].Expiry.AsTime().Before(time.Now())) + assert.True(t, listAllAfterExpiry[3].Expiry.AsTime().IsZero()) + assert.True(t, listAllAfterExpiry[4].Expiry.AsTime().IsZero()) + + err = scenario.Shutdown() + assert.NoError(t, err) +} + +func TestNodeRenameCommand(t *testing.T) { + IntegrationSkip(t) + t.Parallel() + + scenario, err := NewScenario() + assert.NoError(t, err) + + spec := map[string]int{ + "machine-rename-command": 0, + } + + err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) + assert.NoError(t, err) + + headscale, err := scenario.Headscale() + assert.NoError(t, err) + + // Randomly generated machine keys + machineKeys := []string{ + "nodekey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", + "nodekey:8bc13285cee598acf76b1824a6f4490f7f2e3751b201e28aeb3b07fe81d5b4a1", + "nodekey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507", + "nodekey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c", + "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", + } + machines := make([]*v1.Machine, len(machineKeys)) + assert.Nil(t, err) + + for index, machineKey := range machineKeys { + _, err := headscale.Execute( + []string{ + "headscale", + "debug", + "create-node", + "--name", + fmt.Sprintf("machine-%d", index+1), + "--user", + "machine-rename-command", + "--key", + machineKey, + "--output", + "json", + }, + ) + assert.Nil(t, err) + + var machine v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "--user", + "machine-rename-command", + "register", + "--key", + machineKey, + "--output", + "json", + }, + &machine, + ) + assert.Nil(t, err) + + machines[index] = &machine + } + + assert.Len(t, machines, len(machineKeys)) + + var listAll []v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--output", + "json", + }, + &listAll, + ) + assert.Nil(t, err) + + assert.Len(t, listAll, 5) + + assert.Contains(t, listAll[0].GetGivenName(), "machine-1") + assert.Contains(t, listAll[1].GetGivenName(), "machine-2") + assert.Contains(t, listAll[2].GetGivenName(), "machine-3") + assert.Contains(t, listAll[3].GetGivenName(), "machine-4") + assert.Contains(t, listAll[4].GetGivenName(), "machine-5") + + for idx := 0; idx < 3; idx++ { + _, err := headscale.Execute( + []string{ + "headscale", + "nodes", + "rename", + "--identifier", + fmt.Sprintf("%d", listAll[idx].Id), + fmt.Sprintf("newmachine-%d", idx+1), + }, + ) + assert.Nil(t, err) + } + + var listAllAfterRename []v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--output", + "json", + }, + &listAllAfterRename, + ) + assert.Nil(t, err) + + assert.Len(t, listAllAfterRename, 5) + + assert.Equal(t, "newmachine-1", listAllAfterRename[0].GetGivenName()) + assert.Equal(t, "newmachine-2", listAllAfterRename[1].GetGivenName()) + assert.Equal(t, "newmachine-3", listAllAfterRename[2].GetGivenName()) + assert.Contains(t, listAllAfterRename[3].GetGivenName(), "machine-4") + assert.Contains(t, listAllAfterRename[4].GetGivenName(), "machine-5") + + // Test failure for too long names + result, err := headscale.Execute( + []string{ + "headscale", + "nodes", + "rename", + "--identifier", + fmt.Sprintf("%d", listAll[4].Id), + "testmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaachine12345678901234567890", + }, + ) + assert.Nil(t, err) + assert.Contains(t, result, "not be over 63 chars") + + var listAllAfterRenameAttempt []v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--output", + "json", + }, + &listAllAfterRenameAttempt, + ) + assert.Nil(t, err) + + assert.Len(t, listAllAfterRenameAttempt, 5) + + assert.Equal(t, "newmachine-1", listAllAfterRenameAttempt[0].GetGivenName()) + assert.Equal(t, "newmachine-2", listAllAfterRenameAttempt[1].GetGivenName()) + assert.Equal(t, "newmachine-3", listAllAfterRenameAttempt[2].GetGivenName()) + assert.Contains(t, listAllAfterRenameAttempt[3].GetGivenName(), "machine-4") + assert.Contains(t, listAllAfterRenameAttempt[4].GetGivenName(), "machine-5") + + err = scenario.Shutdown() + assert.NoError(t, err) +} + +func TestNodeMoveCommand(t *testing.T) { + IntegrationSkip(t) + t.Parallel() + + scenario, err := NewScenario() + assert.NoError(t, err) + + spec := map[string]int{ + "old-user": 0, + "new-user": 0, + } + + err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("clins")) + assert.NoError(t, err) + + headscale, err := scenario.Headscale() + assert.NoError(t, err) + + // Randomly generated machine key + machineKey := "nodekey:688411b767663479632d44140f08a9fde87383adc7cdeb518f62ce28a17ef0aa" + + _, err = headscale.Execute( + []string{ + "headscale", + "debug", + "create-node", + "--name", + "nomad-machine", + "--user", + "old-user", + "--key", + machineKey, + "--output", + "json", + }, + ) + assert.Nil(t, err) + + var machine v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "--user", + "old-user", + "register", + "--key", + machineKey, + "--output", + "json", + }, + &machine, + ) + assert.Nil(t, err) + + assert.Equal(t, uint64(1), machine.Id) + assert.Equal(t, "nomad-machine", machine.Name) + assert.Equal(t, machine.User.Name, "old-user") + + machineID := fmt.Sprintf("%d", machine.Id) + + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "move", + "--identifier", + machineID, + "--user", + "new-user", + "--output", + "json", + }, + &machine, + ) + assert.Nil(t, err) + + assert.Equal(t, machine.User.Name, "new-user") + + var allNodes []v1.Machine + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "list", + "--output", + "json", + }, + &allNodes, + ) + assert.Nil(t, err) + + assert.Len(t, allNodes, 1) + + assert.Equal(t, allNodes[0].Id, machine.Id) + assert.Equal(t, allNodes[0].User, machine.User) + assert.Equal(t, allNodes[0].User.Name, "new-user") + + moveToNonExistingNSResult, err := headscale.Execute( + []string{ + "headscale", + "nodes", + "move", + "--identifier", + machineID, + "--user", + "non-existing-user", + "--output", + "json", + }, + ) + assert.Nil(t, err) + + assert.Contains( + t, + moveToNonExistingNSResult, + "User not found", + ) + assert.Equal(t, machine.User.Name, "new-user") + + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "move", + "--identifier", + machineID, + "--user", + "old-user", + "--output", + "json", + }, + &machine, + ) + assert.Nil(t, err) + + assert.Equal(t, machine.User.Name, "old-user") + + err = executeAndUnmarshal( + headscale, + []string{ + "headscale", + "nodes", + "move", + "--identifier", + machineID, + "--user", + "old-user", + "--output", + "json", + }, + &machine, + ) + assert.Nil(t, err) + + assert.Equal(t, machine.User.Name, "old-user") + + err = scenario.Shutdown() + assert.NoError(t, err) +} diff --git a/integration/embedded_derp_test.go b/integration/embedded_derp_test.go index 5f831bf4..be128087 100644 --- a/integration/embedded_derp_test.go +++ b/integration/embedded_derp_test.go @@ -6,7 +6,7 @@ import ( "net/url" "testing" - "github.com/juanfont/headscale" + "github.com/juanfont/headscale/hscontrol" "github.com/juanfont/headscale/integration/dockertestutil" "github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/tsic" @@ -110,7 +110,7 @@ func (s *EmbeddedDERPServerScenario) CreateHeadscaleEnv( return err } - hash, err := headscale.GenerateRandomStringDNSSafe(scenarioHashLength) + hash, err := hscontrol.GenerateRandomStringDNSSafe(scenarioHashLength) if err != nil { return err } diff --git a/integration/hsic/hsic.go b/integration/hsic/hsic.go index 63034918..6b1652b0 100644 --- a/integration/hsic/hsic.go +++ b/integration/hsic/hsic.go @@ -22,8 +22,8 @@ import ( "time" "github.com/davecgh/go-spew/spew" - "github.com/juanfont/headscale" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol" "github.com/juanfont/headscale/integration/dockertestutil" "github.com/juanfont/headscale/integration/integrationutil" "github.com/ory/dockertest/v3" @@ -59,7 +59,7 @@ type HeadscaleInContainer struct { port int extraPorts []string hostPortBindings map[string][]string - aclPolicy *headscale.ACLPolicy + aclPolicy *hscontrol.ACLPolicy env map[string]string tlsCert []byte tlsKey []byte @@ -70,9 +70,9 @@ type HeadscaleInContainer struct { // Headscale instance. type Option = func(c *HeadscaleInContainer) -// WithACLPolicy adds a headscale.ACLPolicy policy to the +// WithACLPolicy adds a hscontrol.ACLPolicy policy to the // HeadscaleInContainer instance. -func WithACLPolicy(acl *headscale.ACLPolicy) Option { +func WithACLPolicy(acl *hscontrol.ACLPolicy) Option { return func(hsic *HeadscaleInContainer) { // TODO(kradalby): Move somewhere appropriate hsic.env["HEADSCALE_ACL_POLICY_PATH"] = aclPolicyPath @@ -132,7 +132,7 @@ func WithHostPortBindings(bindings map[string][]string) Option { // in the Docker container name. func WithTestName(testName string) Option { return func(hsic *HeadscaleInContainer) { - hash, _ := headscale.GenerateRandomStringDNSSafe(hsicHashLength) + hash, _ := hscontrol.GenerateRandomStringDNSSafe(hsicHashLength) hostname := fmt.Sprintf("hs-%s-%s", testName, hash) hsic.hostname = hostname @@ -167,7 +167,7 @@ func New( network *dockertest.Network, opts ...Option, ) (*HeadscaleInContainer, error) { - hash, err := headscale.GenerateRandomStringDNSSafe(hsicHashLength) + hash, err := hscontrol.GenerateRandomStringDNSSafe(hsicHashLength) if err != nil { return nil, err } diff --git a/integration/scenario.go b/integration/scenario.go index 817ea7c1..58005482 100644 --- a/integration/scenario.go +++ b/integration/scenario.go @@ -9,8 +9,8 @@ import ( "sync" "time" - "github.com/juanfont/headscale" v1 "github.com/juanfont/headscale/gen/go/headscale/v1" + "github.com/juanfont/headscale/hscontrol" "github.com/juanfont/headscale/integration/dockertestutil" "github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/tsic" @@ -105,7 +105,7 @@ type Scenario struct { // NewScenario creates a test Scenario which can be used to bootstraps a ControlServer with // a set of Users and TailscaleClients. func NewScenario() (*Scenario, error) { - hash, err := headscale.GenerateRandomStringDNSSafe(scenarioHashLength) + hash, err := hscontrol.GenerateRandomStringDNSSafe(scenarioHashLength) if err != nil { return nil, err } diff --git a/integration/ssh_test.go b/integration/ssh_test.go index aaebf3b9..922ced62 100644 --- a/integration/ssh_test.go +++ b/integration/ssh_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/juanfont/headscale" + "github.com/juanfont/headscale/hscontrol" "github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/tsic" "github.com/stretchr/testify/assert" @@ -57,18 +57,18 @@ func TestSSHOneUserAllToAll(t *testing.T) { err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{tsic.WithSSH()}, hsic.WithACLPolicy( - &headscale.ACLPolicy{ + &hscontrol.ACLPolicy{ Groups: map[string][]string{ "group:integration-test": {"user1"}, }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"*"}, Destinations: []string{"*:*"}, }, }, - SSHs: []headscale.SSH{ + SSHs: []hscontrol.SSH{ { Action: "accept", Sources: []string{"group:integration-test"}, @@ -134,18 +134,18 @@ func TestSSHMultipleUsersAllToAll(t *testing.T) { err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{tsic.WithSSH()}, hsic.WithACLPolicy( - &headscale.ACLPolicy{ + &hscontrol.ACLPolicy{ Groups: map[string][]string{ "group:integration-test": {"user1", "user2"}, }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"*"}, Destinations: []string{"*:*"}, }, }, - SSHs: []headscale.SSH{ + SSHs: []hscontrol.SSH{ { Action: "accept", Sources: []string{"group:integration-test"}, @@ -216,18 +216,18 @@ func TestSSHNoSSHConfigured(t *testing.T) { err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{tsic.WithSSH()}, hsic.WithACLPolicy( - &headscale.ACLPolicy{ + &hscontrol.ACLPolicy{ Groups: map[string][]string{ "group:integration-test": {"user1"}, }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"*"}, Destinations: []string{"*:*"}, }, }, - SSHs: []headscale.SSH{}, + SSHs: []hscontrol.SSH{}, }, ), hsic.WithTestName("sshnoneconfigured"), @@ -286,18 +286,18 @@ func TestSSHIsBlockedInACL(t *testing.T) { err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{tsic.WithSSH()}, hsic.WithACLPolicy( - &headscale.ACLPolicy{ + &hscontrol.ACLPolicy{ Groups: map[string][]string{ "group:integration-test": {"user1"}, }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"*"}, Destinations: []string{"*:80"}, }, }, - SSHs: []headscale.SSH{ + SSHs: []hscontrol.SSH{ { Action: "accept", Sources: []string{"group:integration-test"}, @@ -364,19 +364,19 @@ func TestSSUserOnlyIsolation(t *testing.T) { err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{tsic.WithSSH()}, hsic.WithACLPolicy( - &headscale.ACLPolicy{ + &hscontrol.ACLPolicy{ Groups: map[string][]string{ "group:ssh1": {"useracl1"}, "group:ssh2": {"useracl2"}, }, - ACLs: []headscale.ACL{ + ACLs: []hscontrol.ACL{ { Action: "accept", Sources: []string{"*"}, Destinations: []string{"*:*"}, }, }, - SSHs: []headscale.SSH{ + SSHs: []hscontrol.SSH{ { Action: "accept", Sources: []string{"group:ssh1"}, diff --git a/integration/tsic/tsic.go b/integration/tsic/tsic.go index 520f0f2d..cc285f3b 100644 --- a/integration/tsic/tsic.go +++ b/integration/tsic/tsic.go @@ -12,7 +12,7 @@ import ( "time" "github.com/cenkalti/backoff/v4" - "github.com/juanfont/headscale" + "github.com/juanfont/headscale/hscontrol" "github.com/juanfont/headscale/integration/dockertestutil" "github.com/juanfont/headscale/integration/integrationutil" "github.com/ory/dockertest/v3" @@ -150,7 +150,7 @@ func New( network *dockertest.Network, opts ...Option, ) (*TailscaleInContainer, error) { - hash, err := headscale.GenerateRandomStringDNSSafe(tsicHashLength) + hash, err := hscontrol.GenerateRandomStringDNSSafe(tsicHashLength) if err != nil { return nil, err } diff --git a/integration_cli_test.go b/integration_cli_test.go deleted file mode 100644 index d6d32569..00000000 --- a/integration_cli_test.go +++ /dev/null @@ -1,1635 +0,0 @@ -// nolint -package headscale - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "os" - "testing" - "time" - - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" -) - -type IntegrationCLITestSuite struct { - suite.Suite - stats *suite.SuiteInformation - - pool dockertest.Pool - network dockertest.Network - headscale dockertest.Resource -} - -func TestIntegrationCLITestSuite(t *testing.T) { - if testing.Short() { - t.Skip("skipping integration tests due to short flag") - } - - s := new(IntegrationCLITestSuite) - - suite.Run(t, s) -} - -func (s *IntegrationCLITestSuite) SetupTest() { - var err error - - if ppool, err := dockertest.NewPool(""); err == nil { - s.pool = *ppool - } else { - s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "") - } - - network, err := GetFirstOrCreateNetwork(&s.pool, headscaleNetwork) - if err != nil { - s.FailNow(fmt.Sprintf("Failed to create or get network: %s", err), "") - } - s.network = network - - headscaleBuildOptions := &dockertest.BuildOptions{ - Dockerfile: "Dockerfile", - ContextDir: ".", - } - - currentPath, err := os.Getwd() - if err != nil { - s.FailNow(fmt.Sprintf("Could not determine current path: %s", err), "") - } - - headscaleOptions := &dockertest.RunOptions{ - Name: "headscale-cli", - Mounts: []string{ - fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath), - }, - Cmd: []string{"headscale", "serve"}, - Networks: []*dockertest.Network{&s.network}, - ExposedPorts: []string{"8080/tcp"}, - PortBindings: map[docker.Port][]docker.PortBinding{ - "8080/tcp": {{HostPort: "8080"}}, - }, - } - - err = s.pool.RemoveContainerByName(headscaleHostname) - if err != nil { - s.FailNow( - fmt.Sprintf( - "Could not remove existing container before building test: %s", - err, - ), - "", - ) - } - - fmt.Println("Creating headscale container for CLI tests") - if pheadscale, err := s.pool.BuildAndRunWithBuildOptions(headscaleBuildOptions, headscaleOptions, DockerRestartPolicy); err == nil { - s.headscale = *pheadscale - } else { - s.FailNow(fmt.Sprintf("Could not start headscale container: %s", err), "") - } - fmt.Println("Created headscale container for CLI tests") - - fmt.Println("Waiting for headscale to be ready for CLI tests") - hostEndpoint := fmt.Sprintf("%s:%s", - s.headscale.GetIPInNetwork(&s.network), - s.headscale.GetPort("8080/tcp")) - - if err := s.pool.Retry(func() error { - url := fmt.Sprintf("http://%s/health", hostEndpoint) - resp, err := http.Get(url) - if err != nil { - fmt.Printf("headscale for CLI test is not ready: %s\n", err) - return err - } - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("status code not OK") - } - - return nil - }); err != nil { - // TODO(kradalby): If we cannot access headscale, or any other fatal error during - // test setup, we need to abort and tear down. However, testify does not seem to - // support that at the moment: - // https://github.com/stretchr/testify/issues/849 - return // fmt.Errorf("Could not connect to headscale: %s", err) - } - fmt.Println("headscale container is ready for CLI tests") -} - -func (s *IntegrationCLITestSuite) TearDownTest() { - if err := s.pool.Purge(&s.headscale); err != nil { - log.Printf("Could not purge resource: %s\n", err) - } - - if err := s.network.Close(); err != nil { - log.Printf("Could not close network: %s\n", err) - } -} - -func (s *IntegrationCLITestSuite) HandleStats( - suiteName string, - stats *suite.SuiteInformation, -) { - s.stats = stats -} - -func (s *IntegrationCLITestSuite) createUser(name string) (*v1.User, error) { - result, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "users", - "create", - name, - "--output", - "json", - }, - []string{}, - ) - if err != nil { - return nil, err - } - - var user v1.User - err = json.Unmarshal([]byte(result), &user) - if err != nil { - return nil, err - } - - return &user, nil -} - -func (s *IntegrationCLITestSuite) TestUserCommand() { - names := []string{"user1", "otherspace", "tasty"} - users := make([]*v1.User, len(names)) - - for index, userName := range names { - user, err := s.createUser(userName) - assert.Nil(s.T(), err) - - users[index] = user - } - - assert.Len(s.T(), users, len(names)) - - assert.Equal(s.T(), names[0], users[0].Name) - assert.Equal(s.T(), names[1], users[1].Name) - assert.Equal(s.T(), names[2], users[2].Name) - - // Test list users - listResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "users", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listedUsers []v1.User - err = json.Unmarshal([]byte(listResult), &listedUsers) - assert.Nil(s.T(), err) - - assert.Equal(s.T(), names[0], listedUsers[0].Name) - assert.Equal(s.T(), names[1], listedUsers[1].Name) - assert.Equal(s.T(), names[2], listedUsers[2].Name) - - // Test rename user - renameResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "users", - "rename", - "--output", - "json", - "tasty", - "newname", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var renamedUser v1.User - err = json.Unmarshal([]byte(renameResult), &renamedUser) - assert.Nil(s.T(), err) - - assert.Equal(s.T(), renamedUser.Name, "newname") - - // Test list after rename users - listAfterRenameResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "users", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listedAfterRenameUsers []v1.User - err = json.Unmarshal([]byte(listAfterRenameResult), &listedAfterRenameUsers) - assert.Nil(s.T(), err) - - assert.Equal(s.T(), names[0], listedAfterRenameUsers[0].Name) - assert.Equal(s.T(), names[1], listedAfterRenameUsers[1].Name) - assert.Equal(s.T(), "newname", listedAfterRenameUsers[2].Name) -} - -func (s *IntegrationCLITestSuite) TestPreAuthKeyCommand() { - count := 5 - - user, err := s.createUser("pre-auth-key-user") - - keys := make([]*v1.PreAuthKey, count) - assert.Nil(s.T(), err) - - for i := 0; i < count; i++ { - preAuthResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "preauthkeys", - "--user", - user.Name, - "create", - "--reusable", - "--expiration", - "24h", - "--output", - "json", - "--tags", - "tag:test1,tag:test2", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var preAuthKey v1.PreAuthKey - err = json.Unmarshal([]byte(preAuthResult), &preAuthKey) - assert.Nil(s.T(), err) - - keys[i] = &preAuthKey - } - - assert.Len(s.T(), keys, 5) - - // Test list of keys - listResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "preauthkeys", - "--user", - user.Name, - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listedPreAuthKeys []v1.PreAuthKey - err = json.Unmarshal([]byte(listResult), &listedPreAuthKeys) - assert.Nil(s.T(), err) - - assert.Equal(s.T(), "1", listedPreAuthKeys[0].Id) - assert.Equal(s.T(), "2", listedPreAuthKeys[1].Id) - assert.Equal(s.T(), "3", listedPreAuthKeys[2].Id) - assert.Equal(s.T(), "4", listedPreAuthKeys[3].Id) - assert.Equal(s.T(), "5", listedPreAuthKeys[4].Id) - - assert.NotEmpty(s.T(), listedPreAuthKeys[0].Key) - assert.NotEmpty(s.T(), listedPreAuthKeys[1].Key) - assert.NotEmpty(s.T(), listedPreAuthKeys[2].Key) - assert.NotEmpty(s.T(), listedPreAuthKeys[3].Key) - assert.NotEmpty(s.T(), listedPreAuthKeys[4].Key) - - assert.True(s.T(), listedPreAuthKeys[0].Expiration.AsTime().After(time.Now())) - assert.True(s.T(), listedPreAuthKeys[1].Expiration.AsTime().After(time.Now())) - assert.True(s.T(), listedPreAuthKeys[2].Expiration.AsTime().After(time.Now())) - assert.True(s.T(), listedPreAuthKeys[3].Expiration.AsTime().After(time.Now())) - assert.True(s.T(), listedPreAuthKeys[4].Expiration.AsTime().After(time.Now())) - - assert.True( - s.T(), - listedPreAuthKeys[0].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), - ) - assert.True( - s.T(), - listedPreAuthKeys[1].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), - ) - assert.True( - s.T(), - listedPreAuthKeys[2].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), - ) - assert.True( - s.T(), - listedPreAuthKeys[3].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), - ) - assert.True( - s.T(), - listedPreAuthKeys[4].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), - ) - - // Test that tags are present - for i := 0; i < count; i++ { - assert.Equal(s.T(), listedPreAuthKeys[i].AclTags, []string{"tag:test1", "tag:test2"}) - } - - // Expire three keys - for i := 0; i < 3; i++ { - _, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "preauthkeys", - "--user", - user.Name, - "expire", - listedPreAuthKeys[i].Key, - }, - []string{}, - ) - assert.Nil(s.T(), err) - } - - // Test list pre auth keys after expire - listAfterExpireResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "preauthkeys", - "--user", - user.Name, - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listedAfterExpirePreAuthKeys []v1.PreAuthKey - err = json.Unmarshal([]byte(listAfterExpireResult), &listedAfterExpirePreAuthKeys) - assert.Nil(s.T(), err) - - assert.True( - s.T(), - listedAfterExpirePreAuthKeys[0].Expiration.AsTime().Before(time.Now()), - ) - assert.True( - s.T(), - listedAfterExpirePreAuthKeys[1].Expiration.AsTime().Before(time.Now()), - ) - assert.True( - s.T(), - listedAfterExpirePreAuthKeys[2].Expiration.AsTime().Before(time.Now()), - ) - assert.True( - s.T(), - listedAfterExpirePreAuthKeys[3].Expiration.AsTime().After(time.Now()), - ) - assert.True( - s.T(), - listedAfterExpirePreAuthKeys[4].Expiration.AsTime().After(time.Now()), - ) -} - -func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandWithoutExpiry() { - user, err := s.createUser("pre-auth-key-without-exp-user") - assert.Nil(s.T(), err) - - preAuthResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "preauthkeys", - "--user", - user.Name, - "create", - "--reusable", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var preAuthKey v1.PreAuthKey - err = json.Unmarshal([]byte(preAuthResult), &preAuthKey) - assert.Nil(s.T(), err) - - // Test list of keys - listResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "preauthkeys", - "--user", - user.Name, - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listedPreAuthKeys []v1.PreAuthKey - err = json.Unmarshal([]byte(listResult), &listedPreAuthKeys) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listedPreAuthKeys, 1) - - assert.True(s.T(), listedPreAuthKeys[0].Expiration.AsTime().After(time.Now())) - assert.True( - s.T(), - listedPreAuthKeys[0].Expiration.AsTime().Before(time.Now().Add(time.Minute*70)), - ) -} - -func (s *IntegrationCLITestSuite) TestPreAuthKeyCommandReusableEphemeral() { - user, err := s.createUser("pre-auth-key-reus-ephm-user") - assert.Nil(s.T(), err) - - preAuthReusableResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "preauthkeys", - "--user", - user.Name, - "create", - "--reusable=true", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var preAuthReusableKey v1.PreAuthKey - err = json.Unmarshal([]byte(preAuthReusableResult), &preAuthReusableKey) - assert.Nil(s.T(), err) - - assert.True(s.T(), preAuthReusableKey.GetReusable()) - assert.False(s.T(), preAuthReusableKey.GetEphemeral()) - - preAuthEphemeralResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "preauthkeys", - "--user", - user.Name, - "create", - "--ephemeral=true", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var preAuthEphemeralKey v1.PreAuthKey - err = json.Unmarshal([]byte(preAuthEphemeralResult), &preAuthEphemeralKey) - assert.Nil(s.T(), err) - - assert.True(s.T(), preAuthEphemeralKey.GetEphemeral()) - assert.False(s.T(), preAuthEphemeralKey.GetReusable()) - - // TODO(kradalby): Evaluate if we need a case to test for reusable and ephemeral - // preAuthReusableAndEphemeralResult, err := ExecuteCommand( - // &s.headscale, - // []string{ - // "headscale", - // "preauthkeys", - // "--user", - // user.Name, - // "create", - // "--ephemeral", - // "--reusable", - // "--output", - // "json", - // }, - // []string{}, - // ) - // assert.NotNil(s.T(), err) - - // Test list of keys - listResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "preauthkeys", - "--user", - user.Name, - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listedPreAuthKeys []v1.PreAuthKey - err = json.Unmarshal([]byte(listResult), &listedPreAuthKeys) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listedPreAuthKeys, 2) -} - -func (s *IntegrationCLITestSuite) TestNodeTagCommand() { - user, err := s.createUser("machine-user") - assert.Nil(s.T(), err) - - machineKeys := []string{ - "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", - "nodekey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c", - } - machines := make([]*v1.Machine, len(machineKeys)) - assert.Nil(s.T(), err) - - for index, machineKey := range machineKeys { - _, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "debug", - "create-node", - "--name", - fmt.Sprintf("machine-%d", index+1), - "--user", - user.Name, - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - machineResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "--user", - user.Name, - "register", - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var machine v1.Machine - err = json.Unmarshal([]byte(machineResult), &machine) - assert.Nil(s.T(), err) - - machines[index] = &machine - } - assert.Len(s.T(), machines, len(machineKeys)) - - addTagResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "tag", - "-i", "1", - "-t", "tag:test", - "--output", "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var machine v1.Machine - err = json.Unmarshal([]byte(addTagResult), &machine) - assert.Nil(s.T(), err) - assert.Equal(s.T(), []string{"tag:test"}, machine.ForcedTags) - - // try to set a wrong tag and retrieve the error - wrongTagResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "tag", - "-i", "2", - "-t", "wrong-tag", - "--output", "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - type errOutput struct { - Error string `json:"error"` - } - var errorOutput errOutput - err = json.Unmarshal([]byte(wrongTagResult), &errorOutput) - assert.Nil(s.T(), err) - assert.Contains(s.T(), errorOutput.Error, "tag must start with the string 'tag:'") - - // Test list all nodes after added seconds - listAllResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--output", "json", - }, - []string{}, - ) - resultMachines := make([]*v1.Machine, len(machineKeys)) - assert.Nil(s.T(), err) - json.Unmarshal([]byte(listAllResult), &resultMachines) - found := false - for _, machine := range resultMachines { - if machine.ForcedTags != nil { - for _, tag := range machine.ForcedTags { - if tag == "tag:test" { - found = true - } - } - } - } - assert.Equal( - s.T(), - true, - found, - "should find a machine with the tag 'tag:test' in the list of machines", - ) -} - -func (s *IntegrationCLITestSuite) TestNodeCommand() { - user, err := s.createUser("machine-user") - assert.Nil(s.T(), err) - - secondUser, err := s.createUser("other-user") - assert.Nil(s.T(), err) - - // Randomly generated machine keys - machineKeys := []string{ - "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", - "nodekey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c", - "nodekey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507", - "nodekey:8bc13285cee598acf76b1824a6f4490f7f2e3751b201e28aeb3b07fe81d5b4a1", - "nodekey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", - } - machines := make([]*v1.Machine, len(machineKeys)) - assert.Nil(s.T(), err) - - for index, machineKey := range machineKeys { - _, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "debug", - "create-node", - "--name", - fmt.Sprintf("machine-%d", index+1), - "--user", - user.Name, - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - machineResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "--user", - user.Name, - "register", - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var machine v1.Machine - err = json.Unmarshal([]byte(machineResult), &machine) - assert.Nil(s.T(), err) - - machines[index] = &machine - } - - assert.Len(s.T(), machines, len(machineKeys)) - - // Test list all nodes after added seconds - listAllResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listAll []v1.Machine - err = json.Unmarshal([]byte(listAllResult), &listAll) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listAll, 5) - - assert.Equal(s.T(), uint64(1), listAll[0].Id) - assert.Equal(s.T(), uint64(2), listAll[1].Id) - assert.Equal(s.T(), uint64(3), listAll[2].Id) - assert.Equal(s.T(), uint64(4), listAll[3].Id) - assert.Equal(s.T(), uint64(5), listAll[4].Id) - - assert.Equal(s.T(), "machine-1", listAll[0].Name) - assert.Equal(s.T(), "machine-2", listAll[1].Name) - assert.Equal(s.T(), "machine-3", listAll[2].Name) - assert.Equal(s.T(), "machine-4", listAll[3].Name) - assert.Equal(s.T(), "machine-5", listAll[4].Name) - - otherUserMachineKeys := []string{ - "nodekey:b5b444774186d4217adcec407563a1223929465ee2c68a4da13af0d0185b4f8e", - "nodekey:dc721977ac7415aafa87f7d4574cbe07c6b171834a6d37375782bdc1fb6b3584", - } - otherUserMachines := make([]*v1.Machine, len(otherUserMachineKeys)) - assert.Nil(s.T(), err) - - for index, machineKey := range otherUserMachineKeys { - _, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "debug", - "create-node", - "--name", - fmt.Sprintf("otherUser-machine-%d", index+1), - "--user", - secondUser.Name, - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - machineResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "--user", - secondUser.Name, - "register", - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var machine v1.Machine - err = json.Unmarshal([]byte(machineResult), &machine) - assert.Nil(s.T(), err) - - otherUserMachines[index] = &machine - } - - assert.Len(s.T(), otherUserMachines, len(otherUserMachineKeys)) - - // Test list all nodes after added otherUser - listAllWithotherUserResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listAllWithotherUser []v1.Machine - err = json.Unmarshal( - []byte(listAllWithotherUserResult), - &listAllWithotherUser, - ) - assert.Nil(s.T(), err) - - // All nodes, machines + otherUser - assert.Len(s.T(), listAllWithotherUser, 7) - - assert.Equal(s.T(), uint64(6), listAllWithotherUser[5].Id) - assert.Equal(s.T(), uint64(7), listAllWithotherUser[6].Id) - - assert.Equal(s.T(), "otherUser-machine-1", listAllWithotherUser[5].Name) - assert.Equal(s.T(), "otherUser-machine-2", listAllWithotherUser[6].Name) - - // Test list all nodes after added otherUser - listOnlyotherUserMachineUserResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--user", - secondUser.Name, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listOnlyotherUserMachineUser []v1.Machine - err = json.Unmarshal( - []byte(listOnlyotherUserMachineUserResult), - &listOnlyotherUserMachineUser, - ) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listOnlyotherUserMachineUser, 2) - - assert.Equal(s.T(), uint64(6), listOnlyotherUserMachineUser[0].Id) - assert.Equal(s.T(), uint64(7), listOnlyotherUserMachineUser[1].Id) - - assert.Equal( - s.T(), - "otherUser-machine-1", - listOnlyotherUserMachineUser[0].Name, - ) - assert.Equal( - s.T(), - "otherUser-machine-2", - listOnlyotherUserMachineUser[1].Name, - ) - - // Delete a machines - _, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "delete", - "--identifier", - // Delete the last added machine - "4", - "--output", - "json", - "--force", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - // Test: list main user after machine is deleted - listOnlyMachineUserAfterDeleteResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--user", - user.Name, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listOnlyMachineUserAfterDelete []v1.Machine - err = json.Unmarshal( - []byte(listOnlyMachineUserAfterDeleteResult), - &listOnlyMachineUserAfterDelete, - ) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listOnlyMachineUserAfterDelete, 4) -} - -func (s *IntegrationCLITestSuite) TestNodeExpireCommand() { - user, err := s.createUser("machine-expire-user") - assert.Nil(s.T(), err) - - // Randomly generated machine keys - machineKeys := []string{ - "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", - "nodekey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c", - "nodekey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507", - "nodekey:8bc13285cee598acf76b1824a6f4490f7f2e3751b201e28aeb3b07fe81d5b4a1", - "nodekey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", - } - machines := make([]*v1.Machine, len(machineKeys)) - assert.Nil(s.T(), err) - - for index, machineKey := range machineKeys { - _, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "debug", - "create-node", - "--name", - fmt.Sprintf("machine-%d", index+1), - "--user", - user.Name, - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - machineResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "--user", - user.Name, - "register", - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var machine v1.Machine - err = json.Unmarshal([]byte(machineResult), &machine) - assert.Nil(s.T(), err) - - machines[index] = &machine - } - - assert.Len(s.T(), machines, len(machineKeys)) - - listAllResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listAll []v1.Machine - err = json.Unmarshal([]byte(listAllResult), &listAll) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listAll, 5) - - assert.True(s.T(), listAll[0].Expiry.AsTime().IsZero()) - assert.True(s.T(), listAll[1].Expiry.AsTime().IsZero()) - assert.True(s.T(), listAll[2].Expiry.AsTime().IsZero()) - assert.True(s.T(), listAll[3].Expiry.AsTime().IsZero()) - assert.True(s.T(), listAll[4].Expiry.AsTime().IsZero()) - - for i := 0; i < 3; i++ { - _, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "expire", - "--identifier", - fmt.Sprintf("%d", listAll[i].Id), - }, - []string{}, - ) - assert.Nil(s.T(), err) - } - - listAllAfterExpiryResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listAllAfterExpiry []v1.Machine - err = json.Unmarshal([]byte(listAllAfterExpiryResult), &listAllAfterExpiry) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listAllAfterExpiry, 5) - - assert.True(s.T(), listAllAfterExpiry[0].Expiry.AsTime().Before(time.Now())) - assert.True(s.T(), listAllAfterExpiry[1].Expiry.AsTime().Before(time.Now())) - assert.True(s.T(), listAllAfterExpiry[2].Expiry.AsTime().Before(time.Now())) - assert.True(s.T(), listAllAfterExpiry[3].Expiry.AsTime().IsZero()) - assert.True(s.T(), listAllAfterExpiry[4].Expiry.AsTime().IsZero()) -} - -func (s *IntegrationCLITestSuite) TestNodeRenameCommand() { - user, err := s.createUser("machine-rename-command") - assert.Nil(s.T(), err) - - // Randomly generated machine keys - machineKeys := []string{ - "nodekey:cf7b0fd05da556fdc3bab365787b506fd82d64a70745db70e00e86c1b1c03084", - "nodekey:8bc13285cee598acf76b1824a6f4490f7f2e3751b201e28aeb3b07fe81d5b4a1", - "nodekey:f08305b4ee4250b95a70f3b7504d048d75d899993c624a26d422c67af0422507", - "nodekey:6abd00bb5fdda622db51387088c68e97e71ce58e7056aa54f592b6a8219d524c", - "nodekey:9b2ffa7e08cc421a3d2cca9012280f6a236fd0de0b4ce005b30a98ad930306fe", - } - machines := make([]*v1.Machine, len(machineKeys)) - assert.Nil(s.T(), err) - - for index, machineKey := range machineKeys { - _, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "debug", - "create-node", - "--name", - fmt.Sprintf("machine-%d", index+1), - "--user", - user.Name, - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - machineResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "--user", - user.Name, - "register", - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var machine v1.Machine - err = json.Unmarshal([]byte(machineResult), &machine) - assert.Nil(s.T(), err) - - machines[index] = &machine - } - - assert.Len(s.T(), machines, len(machineKeys)) - - listAllResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listAll []v1.Machine - err = json.Unmarshal([]byte(listAllResult), &listAll) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listAll, 5) - - assert.Contains(s.T(), listAll[0].GetGivenName(), "machine-1") - assert.Contains(s.T(), listAll[1].GetGivenName(), "machine-2") - assert.Contains(s.T(), listAll[2].GetGivenName(), "machine-3") - assert.Contains(s.T(), listAll[3].GetGivenName(), "machine-4") - assert.Contains(s.T(), listAll[4].GetGivenName(), "machine-5") - - for i := 0; i < 3; i++ { - _, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "rename", - "--identifier", - fmt.Sprintf("%d", listAll[i].Id), - fmt.Sprintf("newmachine-%d", i+1), - }, - []string{}, - ) - assert.Nil(s.T(), err) - } - - listAllAfterRenameResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listAllAfterRename []v1.Machine - err = json.Unmarshal([]byte(listAllAfterRenameResult), &listAllAfterRename) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listAllAfterRename, 5) - - assert.Equal(s.T(), "newmachine-1", listAllAfterRename[0].GetGivenName()) - assert.Equal(s.T(), "newmachine-2", listAllAfterRename[1].GetGivenName()) - assert.Equal(s.T(), "newmachine-3", listAllAfterRename[2].GetGivenName()) - assert.Contains(s.T(), listAllAfterRename[3].GetGivenName(), "machine-4") - assert.Contains(s.T(), listAllAfterRename[4].GetGivenName(), "machine-5") - - // Test failure for too long names - result, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "rename", - "--identifier", - fmt.Sprintf("%d", listAll[4].Id), - "testmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaachine12345678901234567890", - }, - []string{}, - ) - assert.Nil(s.T(), err) - assert.Contains(s.T(), result, "not be over 63 chars") - - listAllAfterRenameAttemptResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listAllAfterRenameAttempt []v1.Machine - err = json.Unmarshal( - []byte(listAllAfterRenameAttemptResult), - &listAllAfterRenameAttempt, - ) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listAllAfterRenameAttempt, 5) - - assert.Equal(s.T(), "newmachine-1", listAllAfterRenameAttempt[0].GetGivenName()) - assert.Equal(s.T(), "newmachine-2", listAllAfterRenameAttempt[1].GetGivenName()) - assert.Equal(s.T(), "newmachine-3", listAllAfterRenameAttempt[2].GetGivenName()) - assert.Contains(s.T(), listAllAfterRenameAttempt[3].GetGivenName(), "machine-4") - assert.Contains(s.T(), listAllAfterRenameAttempt[4].GetGivenName(), "machine-5") -} - -func (s *IntegrationCLITestSuite) TestApiKeyCommand() { - count := 5 - - keys := make([]string, count) - - for i := 0; i < count; i++ { - apiResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "apikeys", - "create", - "--expiration", - "24h", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - assert.NotEmpty(s.T(), apiResult) - - // var apiKey v1.ApiKey - // err = json.Unmarshal([]byte(apiResult), &apiKey) - // assert.Nil(s.T(), err) - - keys[i] = apiResult - } - - assert.Len(s.T(), keys, 5) - - // Test list of keys - listResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "apikeys", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listedApiKeys []v1.ApiKey - err = json.Unmarshal([]byte(listResult), &listedApiKeys) - assert.Nil(s.T(), err) - - assert.Len(s.T(), listedApiKeys, 5) - - assert.Equal(s.T(), uint64(1), listedApiKeys[0].Id) - assert.Equal(s.T(), uint64(2), listedApiKeys[1].Id) - assert.Equal(s.T(), uint64(3), listedApiKeys[2].Id) - assert.Equal(s.T(), uint64(4), listedApiKeys[3].Id) - assert.Equal(s.T(), uint64(5), listedApiKeys[4].Id) - - assert.NotEmpty(s.T(), listedApiKeys[0].Prefix) - assert.NotEmpty(s.T(), listedApiKeys[1].Prefix) - assert.NotEmpty(s.T(), listedApiKeys[2].Prefix) - assert.NotEmpty(s.T(), listedApiKeys[3].Prefix) - assert.NotEmpty(s.T(), listedApiKeys[4].Prefix) - - assert.True(s.T(), listedApiKeys[0].Expiration.AsTime().After(time.Now())) - assert.True(s.T(), listedApiKeys[1].Expiration.AsTime().After(time.Now())) - assert.True(s.T(), listedApiKeys[2].Expiration.AsTime().After(time.Now())) - assert.True(s.T(), listedApiKeys[3].Expiration.AsTime().After(time.Now())) - assert.True(s.T(), listedApiKeys[4].Expiration.AsTime().After(time.Now())) - - assert.True( - s.T(), - listedApiKeys[0].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), - ) - assert.True( - s.T(), - listedApiKeys[1].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), - ) - assert.True( - s.T(), - listedApiKeys[2].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), - ) - assert.True( - s.T(), - listedApiKeys[3].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), - ) - assert.True( - s.T(), - listedApiKeys[4].Expiration.AsTime().Before(time.Now().Add(time.Hour*26)), - ) - - expiredPrefixes := make(map[string]bool) - - // Expire three keys - for i := 0; i < 3; i++ { - _, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "apikeys", - "expire", - "--prefix", - listedApiKeys[i].Prefix, - }, - []string{}, - ) - assert.Nil(s.T(), err) - - expiredPrefixes[listedApiKeys[i].Prefix] = true - } - - // Test list pre auth keys after expire - listAfterExpireResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "apikeys", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var listedAfterExpireApiKeys []v1.ApiKey - err = json.Unmarshal([]byte(listAfterExpireResult), &listedAfterExpireApiKeys) - assert.Nil(s.T(), err) - - for index := range listedAfterExpireApiKeys { - if _, ok := expiredPrefixes[listedAfterExpireApiKeys[index].Prefix]; ok { - // Expired - assert.True( - s.T(), - listedAfterExpireApiKeys[index].Expiration.AsTime().Before(time.Now()), - ) - } else { - // Not expired - assert.False( - s.T(), - listedAfterExpireApiKeys[index].Expiration.AsTime().Before(time.Now()), - ) - } - } -} - -func (s *IntegrationCLITestSuite) TestNodeMoveCommand() { - oldUser, err := s.createUser("old-user") - assert.Nil(s.T(), err) - newUser, err := s.createUser("new-user") - assert.Nil(s.T(), err) - - // Randomly generated machine key - machineKey := "nodekey:688411b767663479632d44140f08a9fde87383adc7cdeb518f62ce28a17ef0aa" - - _, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "debug", - "create-node", - "--name", - "nomad-machine", - "--user", - oldUser.Name, - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - machineResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "--user", - oldUser.Name, - "register", - "--key", - machineKey, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var machine v1.Machine - err = json.Unmarshal([]byte(machineResult), &machine) - assert.Nil(s.T(), err) - - assert.Equal(s.T(), uint64(1), machine.Id) - assert.Equal(s.T(), "nomad-machine", machine.Name) - assert.Equal(s.T(), machine.User.Name, oldUser.Name) - - machineId := fmt.Sprintf("%d", machine.Id) - - moveToNewNSResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "move", - "--identifier", - machineId, - "--user", - newUser.Name, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - err = json.Unmarshal([]byte(moveToNewNSResult), &machine) - assert.Nil(s.T(), err) - - assert.Equal(s.T(), machine.User, newUser) - - listAllNodesResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "list", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - var allNodes []v1.Machine - err = json.Unmarshal([]byte(listAllNodesResult), &allNodes) - assert.Nil(s.T(), err) - - assert.Len(s.T(), allNodes, 1) - - assert.Equal(s.T(), allNodes[0].Id, machine.Id) - assert.Equal(s.T(), allNodes[0].User, machine.User) - assert.Equal(s.T(), allNodes[0].User, newUser) - - moveToNonExistingNSResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "move", - "--identifier", - machineId, - "--user", - "non-existing-user", - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - assert.Contains( - s.T(), - string(moveToNonExistingNSResult), - "User not found", - ) - assert.Equal(s.T(), machine.User, newUser) - - moveToOldNSResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "move", - "--identifier", - machineId, - "--user", - oldUser.Name, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - err = json.Unmarshal([]byte(moveToOldNSResult), &machine) - assert.Nil(s.T(), err) - - assert.Equal(s.T(), machine.User, oldUser) - - moveToSameNSResult, _, err := ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "nodes", - "move", - "--identifier", - machineId, - "--user", - oldUser.Name, - "--output", - "json", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - err = json.Unmarshal([]byte(moveToSameNSResult), &machine) - assert.Nil(s.T(), err) - - assert.Equal(s.T(), machine.User, oldUser) -} - -func (s *IntegrationCLITestSuite) TestLoadConfigFromCommand() { - // TODO: make sure defaultConfig is not same as altConfig - defaultConfig, err := os.ReadFile("integration_test/etc/config.dump.gold.yaml") - assert.Nil(s.T(), err) - altConfig, err := os.ReadFile("integration_test/etc/alt-config.dump.gold.yaml") - assert.Nil(s.T(), err) - altEnvConfig, err := os.ReadFile("integration_test/etc/alt-env-config.dump.gold.yaml") - assert.Nil(s.T(), err) - - _, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "dumpConfig", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - defaultDumpConfig, err := os.ReadFile("integration_test/etc/config.dump.yaml") - assert.Nil(s.T(), err) - - assert.YAMLEq(s.T(), string(defaultConfig), string(defaultDumpConfig)) - - _, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "-c", - "/etc/headscale/alt-config.yaml", - "dumpConfig", - }, - []string{}, - ) - assert.Nil(s.T(), err) - - altDumpConfig, err := os.ReadFile("integration_test/etc/config.dump.yaml") - assert.Nil(s.T(), err) - - assert.YAMLEq(s.T(), string(altConfig), string(altDumpConfig)) - - _, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "dumpConfig", - }, - []string{ - "HEADSCALE_CONFIG=/etc/headscale/alt-env-config.yaml", - }, - ) - assert.Nil(s.T(), err) - - altEnvDumpConfig, err := os.ReadFile("integration_test/etc/config.dump.yaml") - assert.Nil(s.T(), err) - - assert.YAMLEq(s.T(), string(altEnvConfig), string(altEnvDumpConfig)) - - _, _, err = ExecuteCommand( - &s.headscale, - []string{ - "headscale", - "-c", - "/etc/headscale/alt-config.yaml", - "dumpConfig", - }, - []string{ - "HEADSCALE_CONFIG=/etc/headscale/alt-env-config.yaml", - }, - ) - assert.Nil(s.T(), err) - - altDumpConfig, err = os.ReadFile("integration_test/etc/config.dump.yaml") - assert.Nil(s.T(), err) - - assert.YAMLEq(s.T(), string(altConfig), string(altDumpConfig)) -} diff --git a/integration_common_test.go b/integration_common_test.go deleted file mode 100644 index edc0280a..00000000 --- a/integration_common_test.go +++ /dev/null @@ -1,271 +0,0 @@ -// nolint -package headscale - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "net/netip" - "os" - "strconv" - "time" - - v1 "github.com/juanfont/headscale/gen/go/headscale/v1" - "github.com/ory/dockertest/v3" - "github.com/ory/dockertest/v3/docker" -) - -const ( - headscaleNetwork = "headscale-test" - headscaleHostname = "headscale" - DOCKER_EXECUTE_TIMEOUT = 10 * time.Second -) - -var ( - errEnvVarEmpty = errors.New("getenv: environment variable empty") - - IpPrefix4 = netip.MustParsePrefix("100.64.0.0/10") - IpPrefix6 = netip.MustParsePrefix("fd7a:115c:a1e0::/48") - - tailscaleVersions = []string{ - "head", - "unstable", - "1.38.4", - "1.36.2", - "1.34.2", - "1.32.3", - "1.30.2", - "1.28.0", - "1.26.2", - "1.24.2", - "1.22.2", - "1.20.4", - "1.18.2", - "1.16.2", - "1.14.3", - "1.12.3", - } -) - -type ExecuteCommandConfig struct { - timeout time.Duration -} - -type ExecuteCommandOption func(*ExecuteCommandConfig) error - -func ExecuteCommandTimeout(timeout time.Duration) ExecuteCommandOption { - return ExecuteCommandOption(func(conf *ExecuteCommandConfig) error { - conf.timeout = timeout - return nil - }) -} - -func ExecuteCommand( - resource *dockertest.Resource, - cmd []string, - env []string, - options ...ExecuteCommandOption, -) (string, string, error) { - var stdout bytes.Buffer - var stderr bytes.Buffer - - execConfig := ExecuteCommandConfig{ - timeout: DOCKER_EXECUTE_TIMEOUT, - } - - for _, opt := range options { - if err := opt(&execConfig); err != nil { - return "", "", fmt.Errorf("execute-command/options: %w", err) - } - } - - 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 stdout.String(), stderr.String(), res.err - } - - if res.exitCode != 0 { - fmt.Println("Command: ", cmd) - fmt.Println("stdout: ", stdout.String()) - fmt.Println("stderr: ", stderr.String()) - - return stdout.String(), stderr.String(), fmt.Errorf( - "command failed with: %s", - stderr.String(), - ) - } - - return stdout.String(), stderr.String(), nil - case <-time.After(execConfig.timeout): - - return stdout.String(), stderr.String(), fmt.Errorf( - "command timed out after %s", - execConfig.timeout, - ) - } -} - -func DockerRestartPolicy(config *docker.HostConfig) { - // set AutoRemove to true so that stopped container goes away by itself on error *immediately*. - // when set to false, containers remain until the end of the integration test. - config.AutoRemove = false - config.RestartPolicy = docker.RestartPolicy{ - Name: "no", - } -} - -func DockerAllowLocalIPv6(config *docker.HostConfig) { - if config.Sysctls == nil { - config.Sysctls = make(map[string]string, 1) - } - config.Sysctls["net.ipv6.conf.all.disable_ipv6"] = "0" -} - -func DockerAllowNetworkAdministration(config *docker.HostConfig) { - config.CapAdd = append(config.CapAdd, "NET_ADMIN") - config.Mounts = append(config.Mounts, docker.HostMount{ - Type: "bind", - Source: "/dev/net/tun", - Target: "/dev/net/tun", - }) -} - -func getDockerBuildOptions(version string) *dockertest.BuildOptions { - var tailscaleBuildOptions *dockertest.BuildOptions - switch version { - case "head": - tailscaleBuildOptions = &dockertest.BuildOptions{ - Dockerfile: "Dockerfile.tailscale-HEAD", - ContextDir: ".", - BuildArgs: []docker.BuildArg{}, - } - case "unstable": - tailscaleBuildOptions = &dockertest.BuildOptions{ - Dockerfile: "Dockerfile.tailscale", - ContextDir: ".", - BuildArgs: []docker.BuildArg{ - { - Name: "TAILSCALE_VERSION", - Value: "*", // Installs the latest version https://askubuntu.com/a/824926 - }, - { - Name: "TAILSCALE_CHANNEL", - Value: "unstable", - }, - }, - } - default: - tailscaleBuildOptions = &dockertest.BuildOptions{ - Dockerfile: "Dockerfile.tailscale", - ContextDir: ".", - BuildArgs: []docker.BuildArg{ - { - Name: "TAILSCALE_VERSION", - Value: version, - }, - { - Name: "TAILSCALE_CHANNEL", - Value: "stable", - }, - }, - } - } - return tailscaleBuildOptions -} - -func getDNSNames( - headscale *dockertest.Resource, -) ([]string, error) { - listAllResult, _, err := ExecuteCommand( - headscale, - []string{ - "headscale", - "nodes", - "list", - "--output", - "json", - }, - []string{}, - ) - if err != nil { - return nil, err - } - - var listAll []v1.Machine - err = json.Unmarshal([]byte(listAllResult), &listAll) - if err != nil { - return nil, err - } - - hostnames := make([]string, len(listAll)) - - for index := range listAll { - hostnames[index] = listAll[index].GetGivenName() - } - - return hostnames, nil -} - -func GetEnvStr(key string) (string, error) { - v := os.Getenv(key) - if v == "" { - return v, errEnvVarEmpty - } - - return v, nil -} - -func GetEnvBool(key string) (bool, error) { - s, err := GetEnvStr(key) - if err != nil { - return false, err - } - v, err := strconv.ParseBool(s) - if err != nil { - return false, err - } - - return v, nil -} - -func GetFirstOrCreateNetwork(pool *dockertest.Pool, name string) (dockertest.Network, error) { - networks, err := pool.NetworksByName(name) - if err != nil || len(networks) == 0 { - if _, err := pool.CreateNetwork(name); err == nil { - // Create does not give us an updated version of the resource, so we need to - // get it again. - networks, err := pool.NetworksByName(name) - if err != nil { - return dockertest.Network{}, err - } - - return networks[0], nil - } - } - - return networks[0], nil -} diff --git a/integration_test/.gitignore b/integration_test/.gitignore deleted file mode 100644 index 4e9cb7a1..00000000 --- a/integration_test/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -derp.yaml -*.sqlite -*.sqlite3 diff --git a/integration_test/etc/alt-config.dump.gold.yaml b/integration_test/etc/alt-config.dump.gold.yaml deleted file mode 100644 index c0665b30..00000000 --- a/integration_test/etc/alt-config.dump.gold.yaml +++ /dev/null @@ -1,56 +0,0 @@ -acl_policy_path: "" -cli: - insecure: false - timeout: 5s -db_path: /tmp/integration_test_db.sqlite3 -db_ssl: false -db_type: sqlite3 -derp: - auto_update_enabled: false - server: - enabled: false - stun: - enabled: true - update_frequency: 1m - urls: - - https://controlplane.tailscale.com/derpmap/default -dns_config: - override_local_dns: true - base_domain: headscale.net - domains: [] - magic_dns: true - nameservers: - - 127.0.0.11 - - 1.1.1.1 -ephemeral_node_inactivity_timeout: 30m -node_update_check_interval: 10s -grpc_allow_insecure: false -grpc_listen_addr: :50443 -ip_prefixes: - - fd7a:115c:a1e0::/48 - - 100.64.0.0/10 -listen_addr: 0.0.0.0:18080 -log: - level: disabled - format: text -logtail: - enabled: false -metrics_listen_addr: 127.0.0.1:19090 -oidc: - expiry: 180d - only_start_if_oidc_is_available: true - scope: - - openid - - profile - - email - strip_email_domain: true - use_expiry_from_token: false -private_key_path: private.key -noise: - private_key_path: noise_private.key -server_url: http://headscale:18080 -tls_letsencrypt_cache_dir: /var/www/.cache -tls_letsencrypt_challenge_type: HTTP-01 -unix_socket: /var/run/headscale.sock -unix_socket_permission: "0o770" -randomize_client_port: false diff --git a/integration_test/etc/alt-config.yaml b/integration_test/etc/alt-config.yaml deleted file mode 100644 index 55567861..00000000 --- a/integration_test/etc/alt-config.yaml +++ /dev/null @@ -1,31 +0,0 @@ -log: - level: trace -acl_policy_path: "" -db_type: sqlite3 -ephemeral_node_inactivity_timeout: 30m -node_update_check_interval: 10s -ip_prefixes: - - fd7a:115c:a1e0::/48 - - 100.64.0.0/10 -dns_config: - override_local_dns: true - base_domain: headscale.net - magic_dns: true - domains: [] - nameservers: - - 127.0.0.11 - - 1.1.1.1 -db_path: /tmp/integration_test_db.sqlite3 -db_ssl: false -private_key_path: private.key -noise: - private_key_path: noise_private.key -listen_addr: 0.0.0.0:18080 -metrics_listen_addr: 127.0.0.1:19090 -server_url: http://headscale:18080 - -derp: - urls: - - https://controlplane.tailscale.com/derpmap/default - auto_update_enabled: false - update_frequency: 1m diff --git a/integration_test/etc/alt-env-config.dump.gold.yaml b/integration_test/etc/alt-env-config.dump.gold.yaml deleted file mode 100644 index 0be5d9ed..00000000 --- a/integration_test/etc/alt-env-config.dump.gold.yaml +++ /dev/null @@ -1,55 +0,0 @@ -acl_policy_path: "" -cli: - insecure: false - timeout: 5s -db_path: /tmp/integration_test_db.sqlite3 -db_ssl: false -db_type: sqlite3 -derp: - auto_update_enabled: false - server: - enabled: false - stun: - enabled: true - update_frequency: 1m - urls: - - https://controlplane.tailscale.com/derpmap/default -dns_config: - override_local_dns: true - base_domain: headscale.net - domains: [] - magic_dns: true - nameservers: - - 1.1.1.1 -ephemeral_node_inactivity_timeout: 30m -node_update_check_interval: 30s -grpc_allow_insecure: false -grpc_listen_addr: :50443 -ip_prefixes: - - fd7a:115c:a1e0::/48 - - 100.64.0.0/10 -listen_addr: 0.0.0.0:18080 -log: - level: disabled - format: text -logtail: - enabled: false -metrics_listen_addr: 127.0.0.1:19090 -oidc: - expiry: 180d - only_start_if_oidc_is_available: true - scope: - - openid - - profile - - email - strip_email_domain: true - use_expiry_from_token: false -private_key_path: private.key -noise: - private_key_path: noise_private.key -server_url: http://headscale:18080 -tls_letsencrypt_cache_dir: /var/www/.cache -tls_letsencrypt_challenge_type: HTTP-01 -unix_socket: /var/run/headscale.sock -unix_socket_permission: "0o770" -randomize_client_port: false diff --git a/integration_test/etc/alt-env-config.yaml b/integration_test/etc/alt-env-config.yaml deleted file mode 100644 index 2410ca64..00000000 --- a/integration_test/etc/alt-env-config.yaml +++ /dev/null @@ -1,30 +0,0 @@ -log: - level: trace -acl_policy_path: "" -db_type: sqlite3 -ephemeral_node_inactivity_timeout: 30m -node_update_check_interval: 30s -ip_prefixes: - - fd7a:115c:a1e0::/48 - - 100.64.0.0/10 -dns_config: - override_local_dns: true - base_domain: headscale.net - magic_dns: true - domains: [] - nameservers: - - 1.1.1.1 -db_path: /tmp/integration_test_db.sqlite3 -db_ssl: false -private_key_path: private.key -noise: - private_key_path: noise_private.key -listen_addr: 0.0.0.0:18080 -metrics_listen_addr: 127.0.0.1:19090 -server_url: http://headscale:18080 - -derp: - urls: - - https://controlplane.tailscale.com/derpmap/default - auto_update_enabled: false - update_frequency: 1m diff --git a/integration_test/etc/config.dump.gold.yaml b/integration_test/etc/config.dump.gold.yaml deleted file mode 100644 index e6a822a5..00000000 --- a/integration_test/etc/config.dump.gold.yaml +++ /dev/null @@ -1,56 +0,0 @@ -acl_policy_path: "" -cli: - insecure: false - timeout: 5s -db_path: /tmp/integration_test_db.sqlite3 -db_ssl: false -db_type: sqlite3 -derp: - auto_update_enabled: false - server: - enabled: false - stun: - enabled: true - update_frequency: 1m - urls: - - https://controlplane.tailscale.com/derpmap/default -dns_config: - override_local_dns: true - base_domain: headscale.net - domains: [] - magic_dns: true - nameservers: - - 127.0.0.11 - - 1.1.1.1 -ephemeral_node_inactivity_timeout: 30m -node_update_check_interval: 10s -grpc_allow_insecure: false -grpc_listen_addr: :50443 -ip_prefixes: - - fd7a:115c:a1e0::/48 - - 100.64.0.0/10 -listen_addr: 0.0.0.0:8080 -log: - format: text - level: disabled -logtail: - enabled: false -metrics_listen_addr: 127.0.0.1:9090 -oidc: - expiry: 180d - only_start_if_oidc_is_available: true - scope: - - openid - - profile - - email - strip_email_domain: true - use_expiry_from_token: false -private_key_path: private.key -noise: - private_key_path: noise_private.key -server_url: http://headscale:8080 -tls_letsencrypt_cache_dir: /var/www/.cache -tls_letsencrypt_challenge_type: HTTP-01 -unix_socket: /var/run/headscale.sock -unix_socket_permission: "0o770" -randomize_client_port: false diff --git a/integration_test/etc/config.yaml b/integration_test/etc/config.yaml deleted file mode 100644 index efe75fec..00000000 --- a/integration_test/etc/config.yaml +++ /dev/null @@ -1,30 +0,0 @@ -log: - level: trace -acl_policy_path: "" -db_type: sqlite3 -ephemeral_node_inactivity_timeout: 30m -node_update_check_interval: 10s -ip_prefixes: - - fd7a:115c:a1e0::/48 - - 100.64.0.0/10 -dns_config: - override_local_dns: true - base_domain: headscale.net - magic_dns: true - domains: [] - nameservers: - - 127.0.0.11 - - 1.1.1.1 -db_path: /tmp/integration_test_db.sqlite3 -private_key_path: private.key -noise: - private_key_path: noise_private.key -listen_addr: 0.0.0.0:8080 -metrics_listen_addr: 127.0.0.1:9090 -server_url: http://headscale:8080 - -derp: - urls: - - https://controlplane.tailscale.com/derpmap/default - auto_update_enabled: false - update_frequency: 1m diff --git a/integration_test/etc_embedded_derp/config.yaml b/integration_test/etc_embedded_derp/config.yaml deleted file mode 100644 index ed4d51a0..00000000 --- a/integration_test/etc_embedded_derp/config.yaml +++ /dev/null @@ -1,31 +0,0 @@ -log_level: trace -acl_policy_path: "" -db_type: sqlite3 -ephemeral_node_inactivity_timeout: 30m -node_update_check_interval: 10s -ip_prefixes: - - fd7a:115c:a1e0::/48 - - 100.64.0.0/10 -dns_config: - base_domain: headscale.net - magic_dns: true - domains: [] - nameservers: - - 1.1.1.1 -db_path: /tmp/integration_test_db.sqlite3 -private_key_path: private.key -noise: - private_key_path: noise_private.key -listen_addr: 0.0.0.0:443 -server_url: https://headscale:443 -tls_cert_path: "/etc/headscale/tls/server.crt" -tls_key_path: "/etc/headscale/tls/server.key" -tls_client_auth_mode: disabled -derp: - server: - enabled: true - region_id: 999 - region_code: "headscale" - region_name: "Headscale Embedded DERP" - - stun_listen_addr: "0.0.0.0:3478" diff --git a/integration_test/etc_embedded_derp/tls/server.crt b/integration_test/etc_embedded_derp/tls/server.crt deleted file mode 100644 index 95556495..00000000 --- a/integration_test/etc_embedded_derp/tls/server.crt +++ /dev/null @@ -1,22 +0,0 @@ - ------BEGIN CERTIFICATE----- -MIIC8jCCAdqgAwIBAgIULbu+UbSTMG/LtxooLLh7BgSEyqEwDQYJKoZIhvcNAQEL -BQAwFDESMBAGA1UEAwwJaGVhZHNjYWxlMCAXDTIyMDMwNTE2NDgwM1oYDzI1MjEx -MTA0MTY0ODAzWjAUMRIwEAYDVQQDDAloZWFkc2NhbGUwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDqcfpToLZUF0rlNwXkkt3lbyw4Cl4TJdx36o2PKaOK -U+tze/IjRsCWeMwrcR1o9TNZcxsD+c2J48D1WATuQJlMeg+2UJXGaTGRKkkbPMy3 -5m7AFf/Q16UEOgm2NYjZaQ8faRGIMYURG/6sXmNeETJvBixpBev9yKJuVXgqHNS4 -NpEkNwdOCuAZXrmw0HCbiusawJOay4tFvhH14rav8Uimonl8UTNVXufMzyUOuoaQ -TGflmzYX3hIoswRnTPlIWFoqObvx2Q8H+of3uQJXy0m8I6OrIoXLNxnqYMfFls79 -9SYgVc2jPsCbh5fwyRbx2Hof7sIZ1K/mNgxJRG1E3ZiLAgMBAAGjOjA4MBQGA1Ud -EQQNMAuCCWhlYWRzY2FsZTALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH -AwEwDQYJKoZIhvcNAQELBQADggEBANGlVN7NCsJaKz0k0nhlRGK+tcxn2p1PXN/i -Iy+JX8ahixPC4ocRwOhrXgb390ZXLLwq08HrWYRB/Wi1VUzCp5d8dVxvrR43dJ+v -L2EOBiIKgcu2C3pWW1qRR46/EoXUU9kSH2VNBvIhNufi32kEOidoDzxtQf6qVCoF -guUt1JkAqrynv1UvR/2ZRM/WzM/oJ8qfECwrwDxyYhkqU5Z5jCWg0C6kPIBvNdzt -B0eheWS+ZxVwkePTR4e17kIafwknth3lo+orxVrq/xC+OVM1bGrt2ZyD64ZvEqQl -w6kgbzBdLScAQptWOFThwhnJsg0UbYKimZsnYmjVEuN59TJv92M= ------END CERTIFICATE----- - -(Expires on Nov 4 16:48:03 2521 GMT) - diff --git a/integration_test/etc_embedded_derp/tls/server.key b/integration_test/etc_embedded_derp/tls/server.key deleted file mode 100644 index 8a2df34b..00000000 --- a/integration_test/etc_embedded_derp/tls/server.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDqcfpToLZUF0rl -NwXkkt3lbyw4Cl4TJdx36o2PKaOKU+tze/IjRsCWeMwrcR1o9TNZcxsD+c2J48D1 -WATuQJlMeg+2UJXGaTGRKkkbPMy35m7AFf/Q16UEOgm2NYjZaQ8faRGIMYURG/6s -XmNeETJvBixpBev9yKJuVXgqHNS4NpEkNwdOCuAZXrmw0HCbiusawJOay4tFvhH1 -4rav8Uimonl8UTNVXufMzyUOuoaQTGflmzYX3hIoswRnTPlIWFoqObvx2Q8H+of3 -uQJXy0m8I6OrIoXLNxnqYMfFls799SYgVc2jPsCbh5fwyRbx2Hof7sIZ1K/mNgxJ -RG1E3ZiLAgMBAAECggEBALu1Ni/u5Qy++YA8ZcN0s6UXNdhItLmv/q0kZuLQ+9et -CT8VZfFInLndTdsaXenDKLHdryunviFA8SV+q7P2lMbek+Xs735EiyMnMBFWxLIZ -FWNGOeQERGL19QCmLEOmEi2b+iWJQHlKaMWpbPXL3w11a+lKjIBNO4ALfoJ5QveZ -cGMKsJdm/mpqBvLeNeh2eAFk3Gp6sT1g80Ge8NkgyzFBNIqnut0eerM15kPTc6Qz -12JLaOXUuV3PrcB4PN4nOwrTDg88GDNOQtc1Pc9r4nOHyLfr8X7QEtj1wXSwmOuK -d6ynMnAmoxVA9wEnupLbil1bzohRzpsTpkmDruYaBEECgYEA/Z09I8D6mt2NVqIE -KyvLjBK39ijSV9r3/lvB2Ple2OOL5YQEd+yTrIFy+3zdUnDgD1zmNnXjmjvHZ9Lc -IFf2o06AF84QLNB5gLPdDQkGNFdDqUxljBrfAfE3oANmPS/B0SijMGOOOiDO2FtO -xl1nfRr78mswuRs9awoUWCdNRKUCgYEA7KaTYKIQW/FEjw9lshp74q5vbn6zoXF5 -7N8VkwI+bBVNvRbM9XZ8qhfgRdu9eXs5oL/N4mSYY54I8fA//pJ0Z2vpmureMm1V -mL5WBUmSD9DIbAchoK+sRiQhVmNMBQC6cHMABA7RfXvBeGvWrm9pKCS6ZLgLjkjp -PsmAcaXQcW8CgYEA2inAxljjOwUK6FNGsrxhxIT1qtNC3kCGxE+6WSNq67gSR8Vg -8qiX//T7LEslOB3RIGYRwxd2St7RkgZZRZllmOWWWuPwFhzf6E7RAL2akLvggGov -kG4tGEagSw2hjVDfsUT73ExHtMk0Jfmlsg33UC8+PDLpHtLH6qQpDAwC8+ECgYEA -o+AqOIWhvHmT11l7O915Ip1WzvZwYADbxLsrDnVEUsZh4epTHjvh0kvcY6PqTqCV -ZIrOANNWb811Nkz/k8NJVoD08PFp0xPBbZeIq/qpachTsfMyRzq/mobUiyUR9Hjv -ooUQYr78NOApNsG+lWbTNBhS9wI4BlzZIECbcJe5g4MCgYEAndRoy8S+S0Hx/S8a -O3hzXeDmivmgWqn8NVD4AKOovpkz4PaIVVQbAQkiNfAx8/DavPvjEKAbDezJ4ECV -j7IsOWtDVI7pd6eF9fTcECwisrda8aUoiOap8AQb48153Vx+g2N4Vy3uH0xJs4cz -TDALZPOBg8VlV+HEFDP43sp9Bf0= ------END PRIVATE KEY----- diff --git a/integration_test/etc_oidc/base_config.yaml b/integration_test/etc_oidc/base_config.yaml deleted file mode 100644 index cbfac529..00000000 --- a/integration_test/etc_oidc/base_config.yaml +++ /dev/null @@ -1,21 +0,0 @@ -log_level: trace -acl_policy_path: "" -db_type: sqlite3 -ephemeral_node_inactivity_timeout: 30m -node_update_check_interval: 10s -ip_prefixes: - - fd7a:115c:a1e0::/48 - - 100.64.0.0/10 -db_path: /tmp/integration_test_db.sqlite3 -private_key_path: private.key -noise: - private_key_path: noise_private.key -listen_addr: 0.0.0.0:8443 -server_url: https://headscale-oidc:8443 -tls_cert_path: "/etc/headscale/tls/server.crt" -tls_key_path: "/etc/headscale/tls/server.key" -derp: - urls: - - https://controlplane.tailscale.com/derpmap/default - auto_update_enabled: true - update_frequency: 1m diff --git a/integration_test/etc_oidc/tls/server.crt b/integration_test/etc_oidc/tls/server.crt deleted file mode 100644 index 95556495..00000000 --- a/integration_test/etc_oidc/tls/server.crt +++ /dev/null @@ -1,22 +0,0 @@ - ------BEGIN CERTIFICATE----- -MIIC8jCCAdqgAwIBAgIULbu+UbSTMG/LtxooLLh7BgSEyqEwDQYJKoZIhvcNAQEL -BQAwFDESMBAGA1UEAwwJaGVhZHNjYWxlMCAXDTIyMDMwNTE2NDgwM1oYDzI1MjEx -MTA0MTY0ODAzWjAUMRIwEAYDVQQDDAloZWFkc2NhbGUwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDqcfpToLZUF0rlNwXkkt3lbyw4Cl4TJdx36o2PKaOK -U+tze/IjRsCWeMwrcR1o9TNZcxsD+c2J48D1WATuQJlMeg+2UJXGaTGRKkkbPMy3 -5m7AFf/Q16UEOgm2NYjZaQ8faRGIMYURG/6sXmNeETJvBixpBev9yKJuVXgqHNS4 -NpEkNwdOCuAZXrmw0HCbiusawJOay4tFvhH14rav8Uimonl8UTNVXufMzyUOuoaQ -TGflmzYX3hIoswRnTPlIWFoqObvx2Q8H+of3uQJXy0m8I6OrIoXLNxnqYMfFls79 -9SYgVc2jPsCbh5fwyRbx2Hof7sIZ1K/mNgxJRG1E3ZiLAgMBAAGjOjA4MBQGA1Ud -EQQNMAuCCWhlYWRzY2FsZTALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH -AwEwDQYJKoZIhvcNAQELBQADggEBANGlVN7NCsJaKz0k0nhlRGK+tcxn2p1PXN/i -Iy+JX8ahixPC4ocRwOhrXgb390ZXLLwq08HrWYRB/Wi1VUzCp5d8dVxvrR43dJ+v -L2EOBiIKgcu2C3pWW1qRR46/EoXUU9kSH2VNBvIhNufi32kEOidoDzxtQf6qVCoF -guUt1JkAqrynv1UvR/2ZRM/WzM/oJ8qfECwrwDxyYhkqU5Z5jCWg0C6kPIBvNdzt -B0eheWS+ZxVwkePTR4e17kIafwknth3lo+orxVrq/xC+OVM1bGrt2ZyD64ZvEqQl -w6kgbzBdLScAQptWOFThwhnJsg0UbYKimZsnYmjVEuN59TJv92M= ------END CERTIFICATE----- - -(Expires on Nov 4 16:48:03 2521 GMT) - diff --git a/integration_test/etc_oidc/tls/server.key b/integration_test/etc_oidc/tls/server.key deleted file mode 100644 index 8a2df34b..00000000 --- a/integration_test/etc_oidc/tls/server.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDqcfpToLZUF0rl -NwXkkt3lbyw4Cl4TJdx36o2PKaOKU+tze/IjRsCWeMwrcR1o9TNZcxsD+c2J48D1 -WATuQJlMeg+2UJXGaTGRKkkbPMy35m7AFf/Q16UEOgm2NYjZaQ8faRGIMYURG/6s -XmNeETJvBixpBev9yKJuVXgqHNS4NpEkNwdOCuAZXrmw0HCbiusawJOay4tFvhH1 -4rav8Uimonl8UTNVXufMzyUOuoaQTGflmzYX3hIoswRnTPlIWFoqObvx2Q8H+of3 -uQJXy0m8I6OrIoXLNxnqYMfFls799SYgVc2jPsCbh5fwyRbx2Hof7sIZ1K/mNgxJ -RG1E3ZiLAgMBAAECggEBALu1Ni/u5Qy++YA8ZcN0s6UXNdhItLmv/q0kZuLQ+9et -CT8VZfFInLndTdsaXenDKLHdryunviFA8SV+q7P2lMbek+Xs735EiyMnMBFWxLIZ -FWNGOeQERGL19QCmLEOmEi2b+iWJQHlKaMWpbPXL3w11a+lKjIBNO4ALfoJ5QveZ -cGMKsJdm/mpqBvLeNeh2eAFk3Gp6sT1g80Ge8NkgyzFBNIqnut0eerM15kPTc6Qz -12JLaOXUuV3PrcB4PN4nOwrTDg88GDNOQtc1Pc9r4nOHyLfr8X7QEtj1wXSwmOuK -d6ynMnAmoxVA9wEnupLbil1bzohRzpsTpkmDruYaBEECgYEA/Z09I8D6mt2NVqIE -KyvLjBK39ijSV9r3/lvB2Ple2OOL5YQEd+yTrIFy+3zdUnDgD1zmNnXjmjvHZ9Lc -IFf2o06AF84QLNB5gLPdDQkGNFdDqUxljBrfAfE3oANmPS/B0SijMGOOOiDO2FtO -xl1nfRr78mswuRs9awoUWCdNRKUCgYEA7KaTYKIQW/FEjw9lshp74q5vbn6zoXF5 -7N8VkwI+bBVNvRbM9XZ8qhfgRdu9eXs5oL/N4mSYY54I8fA//pJ0Z2vpmureMm1V -mL5WBUmSD9DIbAchoK+sRiQhVmNMBQC6cHMABA7RfXvBeGvWrm9pKCS6ZLgLjkjp -PsmAcaXQcW8CgYEA2inAxljjOwUK6FNGsrxhxIT1qtNC3kCGxE+6WSNq67gSR8Vg -8qiX//T7LEslOB3RIGYRwxd2St7RkgZZRZllmOWWWuPwFhzf6E7RAL2akLvggGov -kG4tGEagSw2hjVDfsUT73ExHtMk0Jfmlsg33UC8+PDLpHtLH6qQpDAwC8+ECgYEA -o+AqOIWhvHmT11l7O915Ip1WzvZwYADbxLsrDnVEUsZh4epTHjvh0kvcY6PqTqCV -ZIrOANNWb811Nkz/k8NJVoD08PFp0xPBbZeIq/qpachTsfMyRzq/mobUiyUR9Hjv -ooUQYr78NOApNsG+lWbTNBhS9wI4BlzZIECbcJe5g4MCgYEAndRoy8S+S0Hx/S8a -O3hzXeDmivmgWqn8NVD4AKOovpkz4PaIVVQbAQkiNfAx8/DavPvjEKAbDezJ4ECV -j7IsOWtDVI7pd6eF9fTcECwisrda8aUoiOap8AQb48153Vx+g2N4Vy3uH0xJs4cz -TDALZPOBg8VlV+HEFDP43sp9Bf0= ------END PRIVATE KEY----- diff --git a/mkdocs.yml b/mkdocs.yml index 43fe4041..75abcddb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -122,12 +122,14 @@ markdown_extensions: # Page tree nav: - Home: index.md + - FAQ: faq.md - Getting started: - Installation: - Linux: running-headscale-linux.md - OpenBSD: running-headscale-openbsd.md - Container: running-headscale-container.md - Configuration: + - Web UI: web-ui.md - OIDC authentication: oidc.md - Exit node: exit-node.md - Reverse proxy: reverse-proxy.md diff --git a/tests/acls/acl_policy_1.hujson b/tests/acls/acl_policy_1.hujson deleted file mode 100644 index dba403f1..00000000 --- a/tests/acls/acl_policy_1.hujson +++ /dev/null @@ -1,127 +0,0 @@ -{ - // Declare static groups of users beyond those in the identity service. - "groups": { - "group:example": [ - "user1@example.com", - "user2@example.com", - ], - "group:example2": [ - "user1@example.com", - "user2@example.com", - ], - }, - // Declare hostname aliases to use in place of IP addresses or subnets. - "hosts": { - "example-host-1": "100.100.100.100", - "example-host-2": "100.100.101.100/24", - }, - // Define who is allowed to use which tags. - "tagOwners": { - // Everyone in the montreal-admins or global-admins group are - // allowed to tag servers as montreal-webserver. - "tag:montreal-webserver": [ - "group:example", - ], - // Only a few admins are allowed to create API servers. - "tag:production": [ - "group:example", - "president@example.com", - ], - }, - // Access control lists. - "acls": [ - // Engineering users, plus the president, can access port 22 (ssh) - // and port 3389 (remote desktop protocol) on all servers, and all - // ports on git-server or ci-server. - { - "action": "accept", - "src": [ - "group:example2", - "192.168.1.0/24" - ], - "dst": [ - "*:22,3389", - "git-server:*", - "ci-server:*" - ], - }, - // Allow engineer users to access any port on a device tagged with - // tag:production. - { - "action": "accept", - "src": [ - "group:example" - ], - "dst": [ - "tag:production:*" - ], - }, - // Allow servers in the my-subnet host and 192.168.1.0/24 to access hosts - // on both networks. - { - "action": "accept", - "src": [ - "example-host-2", - ], - "dst": [ - "example-host-1:*", - "192.168.1.0/24:*" - ], - }, - // Allow every user of your network to access anything on the network. - // Comment out this section if you want to define specific ACL - // restrictions above. - { - "action": "accept", - "src": [ - "*" - ], - "dst": [ - "*:*" - ], - }, - // All users in Montreal are allowed to access the Montreal web - // servers. - { - "action": "accept", - "src": [ - "example-host-1" - ], - "dst": [ - "tag:montreal-webserver:80,443" - ], - }, - // Montreal web servers are allowed to make outgoing connections to - // the API servers, but only on https port 443. - // In contrast, this doesn't grant API servers the right to initiate - // any connections. - { - "action": "accept", - "src": [ - "tag:montreal-webserver" - ], - "dst": [ - "tag:api-server:443" - ], - }, - ], - // Declare tests to check functionality of ACL rules - "tests": [ - { - "src": "user1@example.com", - "accept": [ - "example-host-1:22", - "example-host-2:80" - ], - "deny": [ - "exapmle-host-2:100" - ], - }, - { - "src": "user2@example.com", - "accept": [ - "100.60.3.4:22" - ], - }, - ], -} \ No newline at end of file diff --git a/tests/acls/acl_policy_autoapprovers.hujson b/tests/acls/acl_policy_autoapprovers.hujson deleted file mode 100644 index bf564d88..00000000 --- a/tests/acls/acl_policy_autoapprovers.hujson +++ /dev/null @@ -1,24 +0,0 @@ -// This ACL validates autoApprovers support for -// exit nodes and advertised routes - -{ - "tagOwners": { - "tag:exit": ["test"], - }, - - "groups": { - "group:test": ["test"] - }, - - "acls": [ - {"action": "accept", "users": ["*"], "ports": ["*:*"]}, - ], - - "autoApprovers": { - "exitNode": ["tag:exit"], - "routes": { - "10.10.0.0/16": ["group:test"], - "10.11.0.0/16": ["test"], - } - } -} \ No newline at end of file diff --git a/tests/acls/acl_policy_basic_1.hujson b/tests/acls/acl_policy_basic_1.hujson deleted file mode 100644 index db78ea9c..00000000 --- a/tests/acls/acl_policy_basic_1.hujson +++ /dev/null @@ -1,24 +0,0 @@ -// This ACL is a very basic example to validate the -// expansion of hosts - - -{ - "hosts": { - "host-1": "100.100.100.100", - "subnet-1": "100.100.101.100/24", - }, - - "acls": [ - { - "action": "accept", - "src": [ - "subnet-1", - "192.168.1.0/24" - ], - "dst": [ - "*:22,3389", - "host-1:*", - ], - }, - ], -} \ No newline at end of file diff --git a/tests/acls/acl_policy_basic_groups.hujson b/tests/acls/acl_policy_basic_groups.hujson deleted file mode 100644 index a99568ad..00000000 --- a/tests/acls/acl_policy_basic_groups.hujson +++ /dev/null @@ -1,26 +0,0 @@ -// This ACL is used to test group expansion - -{ - "groups": { - "group:example": [ - "testuser", - ], - }, - - "hosts": { - "host-1": "100.100.100.100", - "subnet-1": "100.100.101.100/24", - }, - - "acls": [ - { - "action": "accept", - "src": [ - "group:example", - ], - "dst": [ - "host-1:*", - ], - }, - ], -} diff --git a/tests/acls/acl_policy_basic_protocols.hujson b/tests/acls/acl_policy_basic_protocols.hujson deleted file mode 100644 index 6772c564..00000000 --- a/tests/acls/acl_policy_basic_protocols.hujson +++ /dev/null @@ -1,41 +0,0 @@ -// This ACL is used to test wildcards - -{ - "hosts": { - "host-1": "100.100.100.100", - "subnet-1": "100.100.101.100/24", - }, - - "acls": [ - { - "Action": "accept", - "src": [ - "*", - ], - "proto": "tcp", - "dst": [ - "host-1:*", - ], - }, - { - "Action": "accept", - "src": [ - "*", - ], - "proto": "udp", - "dst": [ - "host-1:53", - ], - }, - { - "Action": "accept", - "src": [ - "*", - ], - "proto": "icmp", - "dst": [ - "host-1:*", - ], - }, - ], -} \ No newline at end of file diff --git a/tests/acls/acl_policy_basic_range.hujson b/tests/acls/acl_policy_basic_range.hujson deleted file mode 100644 index 2a4208fb..00000000 --- a/tests/acls/acl_policy_basic_range.hujson +++ /dev/null @@ -1,20 +0,0 @@ -// This ACL is used to test the port range expansion - -{ - "hosts": { - "host-1": "100.100.100.100", - "subnet-1": "100.100.101.100/24", - }, - - "acls": [ - { - "action": "accept", - "src": [ - "subnet-1", - ], - "dst": [ - "host-1:5400-5500", - ], - }, - ], -} \ No newline at end of file diff --git a/tests/acls/acl_policy_basic_user_as_user.hujson b/tests/acls/acl_policy_basic_user_as_user.hujson deleted file mode 100644 index 0009364c..00000000 --- a/tests/acls/acl_policy_basic_user_as_user.hujson +++ /dev/null @@ -1,20 +0,0 @@ -// This ACL is used to test namespace expansion - -{ - "hosts": { - "host-1": "100.100.100.100", - "subnet-1": "100.100.101.100/24", - }, - - "acls": [ - { - "action": "accept", - "src": [ - "testuser", - ], - "dst": [ - "host-1:*", - ], - }, - ], -} diff --git a/tests/acls/acl_policy_basic_wildcards.hujson b/tests/acls/acl_policy_basic_wildcards.hujson deleted file mode 100644 index e1a1f714..00000000 --- a/tests/acls/acl_policy_basic_wildcards.hujson +++ /dev/null @@ -1,20 +0,0 @@ -// This ACL is used to test wildcards - -{ - "hosts": { - "host-1": "100.100.100.100", - "subnet-1": "100.100.101.100/24", - }, - - "acls": [ - { - "Action": "accept", - "src": [ - "*", - ], - "dst": [ - "host-1:*", - ], - }, - ], -} \ No newline at end of file diff --git a/tests/acls/acl_policy_basic_wildcards.yaml b/tests/acls/acl_policy_basic_wildcards.yaml deleted file mode 100644 index 4318fcd2..00000000 --- a/tests/acls/acl_policy_basic_wildcards.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -hosts: - host-1: 100.100.100.100/32 - subnet-1: 100.100.101.100/24 -acls: - - action: accept - src: - - "*" - dst: - - host-1:* diff --git a/tests/acls/acl_policy_invalid.hujson b/tests/acls/acl_policy_invalid.hujson deleted file mode 100644 index 3684b1f1..00000000 --- a/tests/acls/acl_policy_invalid.hujson +++ /dev/null @@ -1,125 +0,0 @@ -{ - // Declare static groups of users beyond those in the identity service. - "groups": { - "group:example": [ - "user1@example.com", - "user2@example.com", - ], - }, - // Declare hostname aliases to use in place of IP addresses or subnets. - "hosts": { - "example-host-1": "100.100.100.100", - "example-host-2": "100.100.101.100/24", - }, - // Define who is allowed to use which tags. - "tagOwners": { - // Everyone in the montreal-admins or global-admins group are - // allowed to tag servers as montreal-webserver. - "tag:montreal-webserver": [ - "group:montreal-admins", - "group:global-admins", - ], - // Only a few admins are allowed to create API servers. - "tag:api-server": [ - "group:global-admins", - "example-host-1", - ], - }, - // Access control lists. - "acls": [ - // Engineering users, plus the president, can access port 22 (ssh) - // and port 3389 (remote desktop protocol) on all servers, and all - // ports on git-server or ci-server. - { - "action": "accept", - "src": [ - "group:engineering", - "president@example.com" - ], - "dst": [ - "*:22,3389", - "git-server:*", - "ci-server:*" - ], - }, - // Allow engineer users to access any port on a device tagged with - // tag:production. - { - "action": "accept", - "src": [ - "group:engineers" - ], - "dst": [ - "tag:production:*" - ], - }, - // Allow servers in the my-subnet host and 192.168.1.0/24 to access hosts - // on both networks. - { - "action": "accept", - "src": [ - "my-subnet", - "192.168.1.0/24" - ], - "dst": [ - "my-subnet:*", - "192.168.1.0/24:*" - ], - }, - // Allow every user of your network to access anything on the network. - // Comment out this section if you want to define specific ACL - // restrictions above. - { - "action": "accept", - "src": [ - "*" - ], - "dst": [ - "*:*" - ], - }, - // All users in Montreal are allowed to access the Montreal web - // servers. - { - "action": "accept", - "src": [ - "group:montreal-users" - ], - "dst": [ - "tag:montreal-webserver:80,443" - ], - }, - // Montreal web servers are allowed to make outgoing connections to - // the API servers, but only on https port 443. - // In contrast, this doesn't grant API servers the right to initiate - // any connections. - { - "action": "accept", - "src": [ - "tag:montreal-webserver" - ], - "dst": [ - "tag:api-server:443" - ], - }, - ], - // Declare tests to check functionality of ACL rules - "tests": [ - { - "src": "user1@example.com", - "accept": [ - "example-host-1:22", - "example-host-2:80" - ], - "deny": [ - "exapmle-host-2:100" - ], - }, - { - "src": "user2@example.com", - "accept": [ - "100.60.3.4:22" - ], - }, - ], -} \ No newline at end of file diff --git a/tests/acls/broken.hujson b/tests/acls/broken.hujson deleted file mode 100644 index 98232c64..00000000 --- a/tests/acls/broken.hujson +++ /dev/null @@ -1 +0,0 @@ -{ diff --git a/tests/acls/invalid.hujson b/tests/acls/invalid.hujson deleted file mode 100644 index 733f6924..00000000 --- a/tests/acls/invalid.hujson +++ /dev/null @@ -1,4 +0,0 @@ -{ - "valid_json": true, - "but_a_policy_though": false -} \ No newline at end of file