mirror of
https://github.com/juanfont/headscale.git
synced 2026-02-21 20:20:31 +09:00
Add --disable flag to "headscale nodes expire" CLI command and disable_expiry field handling in the gRPC API to allow disabling key expiry for nodes. When disabled, the node's expiry is set to NULL and IsExpired() returns false. The CLI follows the new grpcRunE/RunE/printOutput patterns introduced in the recent CLI refactor. Also fix NodeSetExpiry to persist directly to the database instead of going through persistNodeToDB which omits the expiry field. Fixes #2681 Co-authored-by: Marco Santos <me@marcopsantos.com>
313 lines
13 KiB
YAML
313 lines
13 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.
|
|
# sqlite: Runs all integration tests with SQLite backend.
|
|
# postgres: Runs a subset of tests with PostgreSQL to verify database compatibility.
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
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 and warm Go cache
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
run: |
|
|
# Build all Go binaries in one nix shell to maximize cache reuse
|
|
nix develop --command -- bash -c '
|
|
go build -o hi ./cmd/hi
|
|
CGO_ENABLED=0 GOOS=linux go build -o headscale ./cmd/headscale
|
|
# Build integration test binary to warm the cache with all dependencies
|
|
go test -c ./integration -o /dev/null 2>/dev/null || true
|
|
'
|
|
- 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: Package Go cache
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
run: |
|
|
# Package Go module cache and build cache
|
|
tar -czf go-cache.tar.gz -C ~ go .cache/go-build
|
|
- name: Upload Go cache
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
|
with:
|
|
name: go-cache
|
|
path: go-cache.tar.gz
|
|
retention-days: 10
|
|
- name: Pin Docker to v28 (avoid v29 breaking changes)
|
|
if: steps.changed-files.outputs.files == 'true'
|
|
run: |
|
|
# Docker 29 breaks docker build via Go client libraries and
|
|
# docker load/save with certain tarball formats.
|
|
# Pin to Docker 28.x until our tooling is updated.
|
|
# https://github.com/actions/runner-images/issues/13474
|
|
sudo install -m 0755 -d /etc/apt/keyrings
|
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
|
|
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
|
|
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
|
|
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
sudo apt-get update -qq
|
|
VERSION=$(apt-cache madison docker-ce | grep '28\.5' | head -1 | awk '{print $3}')
|
|
sudo apt-get install -y --allow-downgrades \
|
|
"docker-ce=${VERSION}" "docker-ce-cli=${VERSION}"
|
|
sudo systemctl restart docker
|
|
docker version
|
|
- 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-latest
|
|
needs: build
|
|
if: needs.build.outputs.files-changed == 'true'
|
|
steps:
|
|
- name: Pin Docker to v28 (avoid v29 breaking changes)
|
|
run: |
|
|
# Docker 29 breaks docker build via Go client libraries and
|
|
# docker load/save with certain tarball formats.
|
|
# Pin to Docker 28.x until our tooling is updated.
|
|
# https://github.com/actions/runner-images/issues/13474
|
|
sudo install -m 0755 -d /etc/apt/keyrings
|
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
|
|
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
|
|
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
|
|
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
sudo apt-get update -qq
|
|
VERSION=$(apt-cache madison docker-ce | grep '28\.5' | head -1 | awk '{print $3}')
|
|
sudo apt-get install -y --allow-downgrades \
|
|
"docker-ce=${VERSION}" "docker-ce-cli=${VERSION}"
|
|
sudo systemctl restart docker
|
|
docker version
|
|
- 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
|
|
sqlite:
|
|
needs: build
|
|
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
|
|
- TestAuthWebFlowLogoutAndReloginNewUser
|
|
- 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
|
|
- TestExpireNode
|
|
- TestSetNodeExpiryInFuture
|
|
- TestDisableNodeExpiry
|
|
- TestNodeOnlineStatus
|
|
- TestPingAllByIPManyUpDown
|
|
- Test2118DeletingOnlineNodePanics
|
|
- TestEnablingRoutes
|
|
- TestHASubnetRouterFailover
|
|
- TestSubnetRouteACL
|
|
- TestEnablingExitRoutes
|
|
- TestSubnetRouterMultiNetwork
|
|
- TestSubnetRouterMultiNetworkExitNode
|
|
- TestAutoApproveMultiNetwork/authkey-tag.*
|
|
- TestAutoApproveMultiNetwork/authkey-user.*
|
|
- TestAutoApproveMultiNetwork/authkey-group.*
|
|
- TestAutoApproveMultiNetwork/webauth-tag.*
|
|
- TestAutoApproveMultiNetwork/webauth-user.*
|
|
- TestAutoApproveMultiNetwork/webauth-group.*
|
|
- TestSubnetRouteACLFiltering
|
|
- TestHeadscale
|
|
- TestTailscaleNodesJoiningHeadcale
|
|
- TestSSHOneUserToAll
|
|
- TestSSHMultipleUsersAllToAll
|
|
- TestSSHNoSSHConfigured
|
|
- TestSSHIsBlockedInACL
|
|
- TestSSHUserOnlyIsolation
|
|
- TestSSHAutogroupSelf
|
|
- TestTagsAuthKeyWithTagRequestDifferentTag
|
|
- TestTagsAuthKeyWithTagNoAdvertiseFlag
|
|
- TestTagsAuthKeyWithTagCannotAddViaCLI
|
|
- TestTagsAuthKeyWithTagCannotChangeViaCLI
|
|
- TestTagsAuthKeyWithTagAdminOverrideReauthPreserves
|
|
- 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
|
|
- TestTagsAuthKeyConvertToUserViaCLIRegister
|
|
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]
|
|
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"
|