mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-31 18:54:11 +09:00
Only a key that speaks for a tagged node re-authenticates it: a tagged
key, or one of the user it was tagged from. Another user's key or login
adds that user's own device, or only wakes the node under its node key.
Fixes ANT-2026-AWGRB7O1
(cherry picked from commit e1de83cbdf, adapted: inline PAK re-register path, no servertest compat table)
400 lines
17 KiB
YAML
400 lines
17 KiB
YAML
name: integration
|
|
# To debug locally on a branch, and when needing secrets
|
|
# change this to include `push` so the build is ran on
|
|
# the main repository.
|
|
on: [pull_request]
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
cancel-in-progress: true
|
|
jobs:
|
|
# build: Builds binaries and Docker images once, uploads as artifacts for reuse.
|
|
# build-postgres: Pulls postgres image separately to avoid Docker Hub rate limits.
|
|
# build-tailscale-released: Pre-pulls released Tailscale images from ghcr.io
|
|
# so fork PRs (no DOCKERHUB_USERNAME secret) don't hit Docker Hub rate
|
|
# limits at test time.
|
|
# sqlite: Runs all integration tests with SQLite backend.
|
|
# postgres: Runs a subset of tests with PostgreSQL to verify database compatibility.
|
|
build:
|
|
runs-on: ubuntu-24.04-arm
|
|
outputs:
|
|
files-changed: ${{ steps.changed-files.outputs.files }}
|
|
steps:
|
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
with:
|
|
fetch-depth: 2
|
|
- name: Get changed files
|
|
id: changed-files
|
|
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
|
with:
|
|
filters: |
|
|
files:
|
|
- '*.nix'
|
|
- 'go.*'
|
|
- '**/*.go'
|
|
- 'integration/**'
|
|
- 'config-example.yaml'
|
|
- '.github/workflows/test-integration.yaml'
|
|
- '.github/workflows/integration-test-template.yml'
|
|
- 'Dockerfile.*'
|
|
- uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
with:
|
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
|
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
|
|
- name: Build binaries
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
run: |
|
|
nix develop --command -- bash -c '
|
|
go build -o hi ./cmd/hi
|
|
CGO_ENABLED=0 GOOS=linux go build -o headscale ./cmd/headscale
|
|
CGO_ENABLED=0 GOOS=linux go test -c ./integration -o integration.test
|
|
'
|
|
- name: Upload hi binary
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
with:
|
|
name: hi-binary
|
|
path: hi
|
|
retention-days: 10
|
|
- name: Upload integration test binary
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
with:
|
|
name: integration-test-binary
|
|
path: integration.test
|
|
retention-days: 10
|
|
- name: Force overlay2 storage driver
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
run: |
|
|
# Docker 29 runner images default to overlayfs, which breaks
|
|
# docker build via Go SDK libraries and docker save/load
|
|
# tarball formats. overlay2 is the long-standing default.
|
|
# https://github.com/actions/runner-images/issues/13474
|
|
sudo mkdir -p /etc/docker
|
|
echo '{"storage-driver":"overlay2"}' | sudo tee /etc/docker/daemon.json
|
|
sudo systemctl restart docker
|
|
docker version
|
|
- name: Login to Docker Hub
|
|
env:
|
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_CI_USERNAME }}
|
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_CI_TOKEN }}
|
|
if: env.DOCKERHUB_USERNAME != ''
|
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
with:
|
|
username: ${{ env.DOCKERHUB_USERNAME }}
|
|
password: ${{ env.DOCKERHUB_TOKEN }}
|
|
- name: Build headscale image
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
run: |
|
|
docker build \
|
|
--file Dockerfile.integration-ci \
|
|
--tag headscale:${{ github.sha }} \
|
|
.
|
|
docker save headscale:${{ github.sha }} | gzip > headscale-image.tar.gz
|
|
- name: Build tailscale HEAD image
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
run: |
|
|
docker build \
|
|
--file Dockerfile.tailscale-HEAD \
|
|
--tag tailscale-head:${{ github.sha }} \
|
|
.
|
|
docker save tailscale-head:${{ github.sha }} | gzip > tailscale-head-image.tar.gz
|
|
- name: Upload headscale image
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
with:
|
|
name: headscale-image
|
|
path: headscale-image.tar.gz
|
|
retention-days: 10
|
|
- name: Upload tailscale HEAD image
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
with:
|
|
name: tailscale-head-image
|
|
path: tailscale-head-image.tar.gz
|
|
retention-days: 10
|
|
build-postgres:
|
|
runs-on: ubuntu-24.04-arm
|
|
needs: build
|
|
if: needs.build.outputs.files-changed == 'true'
|
|
steps:
|
|
- name: Force overlay2 storage driver
|
|
run: |
|
|
sudo mkdir -p /etc/docker
|
|
echo '{"storage-driver":"overlay2"}' | sudo tee /etc/docker/daemon.json
|
|
sudo systemctl restart docker
|
|
docker version
|
|
- name: Login to Docker Hub
|
|
env:
|
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_CI_USERNAME }}
|
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_CI_TOKEN }}
|
|
if: env.DOCKERHUB_USERNAME != ''
|
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
with:
|
|
username: ${{ env.DOCKERHUB_USERNAME }}
|
|
password: ${{ env.DOCKERHUB_TOKEN }}
|
|
- name: Pull and save postgres image
|
|
run: |
|
|
docker pull postgres:latest
|
|
docker tag postgres:latest postgres:${{ github.sha }}
|
|
docker save postgres:${{ github.sha }} | gzip > postgres-image.tar.gz
|
|
- name: Upload postgres image
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
with:
|
|
name: postgres-image
|
|
path: postgres-image.tar.gz
|
|
retention-days: 10
|
|
build-tailscale-released:
|
|
runs-on: ubuntu-24.04-arm
|
|
needs: build
|
|
if: needs.build.outputs.files-changed == 'true'
|
|
steps:
|
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
- uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34
|
|
- uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6.1.3
|
|
with:
|
|
primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }}
|
|
restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }}
|
|
- name: Force overlay2 storage driver
|
|
run: |
|
|
sudo mkdir -p /etc/docker
|
|
echo '{"storage-driver":"overlay2"}' | sudo tee /etc/docker/daemon.json
|
|
sudo systemctl restart docker
|
|
docker version
|
|
- name: Login to Docker Hub
|
|
env:
|
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_CI_USERNAME }}
|
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_CI_TOKEN }}
|
|
if: env.DOCKERHUB_USERNAME != ''
|
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
with:
|
|
username: ${{ env.DOCKERHUB_USERNAME }}
|
|
password: ${{ env.DOCKERHUB_TOKEN }}
|
|
- name: List Tailscale versions to pre-pull
|
|
id: versions
|
|
run: |
|
|
versions=$(nix develop --command go run ./cmd/hi list-versions --set=must --exclude=head)
|
|
echo "versions=${versions}" >> "$GITHUB_OUTPUT"
|
|
echo "Pre-pulling: ${versions}"
|
|
- name: Pull Tailscale images
|
|
run: |
|
|
# Releases come from ghcr.io (anonymous, unmetered). The
|
|
# "unstable" floating tag on ghcr.io has been stale since 2022,
|
|
# so it still needs to come from Docker Hub. xargs -P 0 fans
|
|
# out one process per tag and returns non-zero if any pull
|
|
# fails.
|
|
refs=""
|
|
for v in ${{ steps.versions.outputs.versions }}; do
|
|
if [ "${v}" = "unstable" ]; then
|
|
refs="${refs} tailscale/tailscale:${v}"
|
|
else
|
|
refs="${refs} ghcr.io/tailscale/tailscale:${v}"
|
|
fi
|
|
done
|
|
echo "${refs}" | tr ' ' '\n' | grep -v '^$' \
|
|
| xargs -P 0 -I{} docker pull "{}"
|
|
echo "REFS=${refs}" >> "$GITHUB_ENV"
|
|
- name: Save Tailscale images to tarball
|
|
run: |
|
|
# Single docker save with all refs: one consistent snapshot, no
|
|
# parallel-daemon race.
|
|
docker save ${REFS} | gzip > tailscale-released-images.tar.gz
|
|
ls -lh tailscale-released-images.tar.gz
|
|
- name: Upload Tailscale released images
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
with:
|
|
name: tailscale-released-images
|
|
path: tailscale-released-images.tar.gz
|
|
retention-days: 10
|
|
sqlite:
|
|
needs: [build, build-tailscale-released]
|
|
if: needs.build.outputs.files-changed == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
test:
|
|
- TestACLHostsInNetMapTable
|
|
- TestACLAllowUser80Dst
|
|
- TestACLDenyAllPort80
|
|
- TestACLAllowUserDst
|
|
- TestACLAllowStarDst
|
|
- TestACLNamedHostsCanReachBySubnet
|
|
- TestACLNamedHostsCanReach
|
|
- TestACLDevice1CanAccessDevice2
|
|
- TestPolicyUpdateWhileRunningWithCLIInDatabase
|
|
- TestACLAutogroupMember
|
|
- TestACLAutogroupTagged
|
|
- TestACLAutogroupSelf
|
|
- TestACLPolicyPropagationOverTime
|
|
- TestACLTagPropagation
|
|
- TestACLTagPropagationPortSpecific
|
|
- TestACLGroupWithUnknownUser
|
|
- TestACLGroupAfterUserDeletion
|
|
- TestACLGroupDeletionExactReproduction
|
|
- TestACLDynamicUnknownUserAddition
|
|
- TestACLDynamicUnknownUserRemoval
|
|
- TestAPIAuthenticationBypass
|
|
- TestAPIAuthenticationBypassCurl
|
|
- TestGRPCAuthenticationBypass
|
|
- TestCLIWithConfigAuthenticationBypass
|
|
- TestAuthKeyLogoutAndReloginSameUser
|
|
- TestAuthKeyLogoutAndReloginNewUser
|
|
- TestAuthKeyLogoutAndReloginSameUserExpiredKey
|
|
- TestAuthKeyDeleteKey
|
|
- TestAuthKeyLogoutAndReloginRoutesPreserved
|
|
- TestOIDCAuthenticationPingAll
|
|
- TestOIDCExpireNodesBasedOnTokenExpiry
|
|
- TestOIDC024UserCreation
|
|
- TestOIDCAuthenticationWithPKCE
|
|
- TestOIDCReloginSameNodeNewUser
|
|
- TestOIDCFollowUpUrl
|
|
- TestOIDCMultipleOpenedLoginUrls
|
|
- TestOIDCReloginSameNodeSameUser
|
|
- TestOIDCExpiryAfterRestart
|
|
- TestOIDCACLPolicyOnJoin
|
|
- TestOIDCReloginSameUserRoutesPreserved
|
|
- TestAuthWebFlowAuthenticationPingAll
|
|
- TestAuthWebFlowLogoutAndReloginSameUser
|
|
- TestAuthWebFlowReloginExpiredNode
|
|
- TestAuthWebFlowLogoutAndReloginNewUser
|
|
- TestPolicyCheckCommand
|
|
- TestSSHTestsRejectFailingPolicy
|
|
- TestUserCommand
|
|
- TestPreAuthKeyCommand
|
|
- TestPreAuthKeyCommandWithoutExpiry
|
|
- TestPreAuthKeyCommandReusableEphemeral
|
|
- TestPreAuthKeyCorrectUserLoggedInCommand
|
|
- TestTaggedNodesCLIOutput
|
|
- TestApiKeyCommand
|
|
- TestNodeCommand
|
|
- TestNodeExpireCommand
|
|
- TestNodeRenameCommand
|
|
- TestPolicyCommand
|
|
- TestPolicyBrokenConfigCommand
|
|
- TestDERPVerifyEndpoint
|
|
- TestResolveMagicDNS
|
|
- TestResolveMagicDNSExtraRecordsPath
|
|
- TestDERPServerScenario
|
|
- TestDERPServerWebsocketScenario
|
|
- TestPingAllByIP
|
|
- TestPingAllByIPPublicDERP
|
|
- TestEphemeral
|
|
- TestEphemeralInAlternateTimezone
|
|
- TestEphemeral2006DeletedTooQuickly
|
|
- TestPingAllByHostname
|
|
- TestTaildrop
|
|
- TestUpdateHostnameFromClient
|
|
- TestHostinfoChangeReachesPeersOnlyWhenPeerVisible
|
|
- TestExpireNode
|
|
- TestSetNodeExpiryInFuture
|
|
- TestDisableNodeExpiry
|
|
- TestNodeOnlineStatus
|
|
- TestPingAllByIPManyUpDown
|
|
- TestNodeDeletionEndsLongPoll
|
|
- Test2118DeletingOnlineNodePanics
|
|
- TestGrantCapRelay
|
|
- TestGrantCapDrive
|
|
- TestEnablingRoutes
|
|
- TestHASubnetRouterFailover
|
|
- TestSubnetRouteACL
|
|
- TestEnablingExitRoutes
|
|
- TestExitRoutesWithAutogroupInternetACL
|
|
- TestSubnetRouterMultiNetwork
|
|
- TestSubnetRouterMultiNetworkExitNode
|
|
- TestAutoApproveMultiNetwork/authkey-tag.*
|
|
- TestAutoApproveMultiNetwork/authkey-user.*
|
|
- TestAutoApproveMultiNetwork/authkey-group.*
|
|
- TestAutoApproveMultiNetwork/webauth-tag.*
|
|
- TestAutoApproveMultiNetwork/webauth-user.*
|
|
- TestAutoApproveMultiNetwork/webauth-group.*
|
|
- TestSubnetRouteACLFiltering
|
|
- TestGrantViaSubnetSteering
|
|
- TestHASubnetRouterPingFailover
|
|
- TestHASubnetRouterFailoverBothOffline
|
|
- TestHASubnetRouterFailoverBothOfflineCablePull
|
|
- TestHASubnetRouterFailoverDockerDisconnect
|
|
- TestHeadscale
|
|
- TestTailscaleNodesJoiningHeadcale
|
|
- TestSSHOneUserToAll
|
|
- TestSSHMultipleUsersAllToAll
|
|
- TestSSHNoSSHConfigured
|
|
- TestSSHRulesRemovedDeniesAccess
|
|
- TestSSHIsBlockedInACL
|
|
- TestSSHUserOnlyIsolation
|
|
- TestSSHAutogroupSelf
|
|
- TestSSHOneUserToOneCheckModeCLI
|
|
- TestSSHOneUserToOneCheckModeOIDC
|
|
- TestSSHCheckModeUnapprovedTimeout
|
|
- TestSSHCheckModeCheckPeriodCLI
|
|
- TestSSHCheckModeAutoApprove
|
|
- TestSSHCheckModeSessionLossReDelegates
|
|
- TestSSHCheckModeNegativeCLI
|
|
- TestSSHLocalpart
|
|
- TestTagsAuthKeyWithTagRequestDifferentTag
|
|
- TestTagsAuthKeyWithTagNoAdvertiseFlag
|
|
- TestTagsAuthKeyWithTagCannotAddViaCLI
|
|
- TestTagsAuthKeyWithTagCannotChangeViaCLI
|
|
- TestTagsAuthKeyWithTagAdminOverrideReauthPreserves
|
|
- TestTagsReauthDifferentKeyRetagsNode
|
|
- TestTagsReauthDifferentKeyRemovesTag
|
|
- TestTagsAuthKeyWithTagCLICannotModifyAdminTags
|
|
- TestTagsAuthKeyWithoutTagCannotRequestTags
|
|
- TestTagsAuthKeyWithoutTagRegisterNoTags
|
|
- TestTagsAuthKeyWithoutTagCannotAddViaCLI
|
|
- TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithReset
|
|
- TestTagsAuthKeyWithoutTagCLINoOpAfterAdminWithEmptyAdvertise
|
|
- TestTagsAuthKeyWithoutTagCLICannotReduceAdminMultiTag
|
|
- TestTagsUserLoginOwnedTagAtRegistration
|
|
- TestTagsUserLoginNonExistentTagAtRegistration
|
|
- TestTagsUserLoginUnownedTagAtRegistration
|
|
- TestTagsUserLoginAddTagViaCLIReauth
|
|
- TestTagsUserLoginRemoveTagViaCLIReauth
|
|
- TestTagsUserLoginCLINoOpAfterAdminAssignment
|
|
- TestTagsUserLoginCLICannotRemoveAdminTags
|
|
- TestTagsAuthKeyWithTagRequestNonExistentTag
|
|
- TestTagsAuthKeyWithTagRequestUnownedTag
|
|
- TestTagsAuthKeyWithoutTagRequestNonExistentTag
|
|
- TestTagsAuthKeyWithoutTagRequestUnownedTag
|
|
- TestTagsAdminAPICannotSetNonExistentTag
|
|
- TestTagsAdminAPICanSetUnownedTag
|
|
- TestTagsAdminAPICannotRemoveAllTags
|
|
- TestTagsIssue2978ReproTagReplacement
|
|
- TestTagsAdminAPICannotSetInvalidFormat
|
|
- TestTagsUserLoginReauthWithEmptyTagsRemovesAllTags
|
|
- TestTagsAuthKeyWithoutUserInheritsTags
|
|
- TestTagsAuthKeyWithoutUserRejectsAdvertisedTags
|
|
- TestTagsAuthKeyWithoutUserCLIRegisterAddsUserDevice
|
|
- TestTaggedNodeLogoutReloginSingleUseKeyOnline
|
|
- TestTaggedNodeLogoutReloginReusableKeyOnline
|
|
- TestTagsOIDCReauthAddOwnedTag
|
|
- TestTagsReauthEmptyTagsReturnsToUserSurvives
|
|
- TestTS2021WebSocketGET
|
|
- TestTS2021WASMClientUnderNode
|
|
- TestTailscaleRustAxum
|
|
uses: ./.github/workflows/integration-test-template.yml
|
|
secrets: inherit
|
|
with:
|
|
test: ${{ matrix.test }}
|
|
postgres_flag: "--postgres=0"
|
|
database_name: "sqlite"
|
|
postgres:
|
|
needs: [build, build-postgres, build-tailscale-released]
|
|
if: needs.build.outputs.files-changed == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
test:
|
|
- TestACLAllowUserDst
|
|
- TestPingAllByIP
|
|
- TestEphemeral2006DeletedTooQuickly
|
|
- TestPingAllByIPManyUpDown
|
|
- TestSubnetRouterMultiNetwork
|
|
uses: ./.github/workflows/integration-test-template.yml
|
|
secrets: inherit
|
|
with:
|
|
test: ${{ matrix.test }}
|
|
postgres_flag: "--postgres=1"
|
|
database_name: "postgres"
|