mirror of
https://github.com/juanfont/headscale.git
synced 2025-02-25 00:15:31 +01:00
fix routes not being saved when new nodes registers (#2444)
* add test to validate exitnode propagation Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * save routes on register Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * update changelog Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * no nil Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add missing integration tests Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
bcff0eaae7
commit
da2ca054b1
2
.github/workflows/test-integration.yaml
vendored
2
.github/workflows/test-integration.yaml
vendored
@ -24,6 +24,7 @@ jobs:
|
|||||||
- TestPolicyUpdateWhileRunningWithCLIInDatabase
|
- TestPolicyUpdateWhileRunningWithCLIInDatabase
|
||||||
- TestAuthKeyLogoutAndReloginSameUser
|
- TestAuthKeyLogoutAndReloginSameUser
|
||||||
- TestAuthKeyLogoutAndReloginNewUser
|
- TestAuthKeyLogoutAndReloginNewUser
|
||||||
|
- TestAuthKeyLogoutAndReloginSameUserExpiredKey
|
||||||
- TestOIDCAuthenticationPingAll
|
- TestOIDCAuthenticationPingAll
|
||||||
- TestOIDCExpireNodesBasedOnTokenExpiry
|
- TestOIDCExpireNodesBasedOnTokenExpiry
|
||||||
- TestOIDC024UserCreation
|
- TestOIDC024UserCreation
|
||||||
@ -68,6 +69,7 @@ jobs:
|
|||||||
- TestEnableDisableAutoApprovedRoute
|
- TestEnableDisableAutoApprovedRoute
|
||||||
- TestAutoApprovedSubRoute2068
|
- TestAutoApprovedSubRoute2068
|
||||||
- TestSubnetRouteACL
|
- TestSubnetRouteACL
|
||||||
|
- TestEnablingExitRoutes
|
||||||
- TestHeadscale
|
- TestHeadscale
|
||||||
- TestCreateTailscale
|
- TestCreateTailscale
|
||||||
- TestTailscaleNodesJoiningHeadcale
|
- TestTailscaleNodesJoiningHeadcale
|
||||||
|
@ -14,12 +14,14 @@
|
|||||||
- View of config, policy, filter, ssh policy per node, connected nodes and
|
- View of config, policy, filter, ssh policy per node, connected nodes and
|
||||||
DERPmap
|
DERPmap
|
||||||
|
|
||||||
## 0.25.1 (2025-02-18)
|
## 0.25.1 (2025-02-24)
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Fix issue where registration errors are sent correctly
|
- Fix issue where registration errors are sent correctly
|
||||||
[#2435](https://github.com/juanfont/headscale/pull/2435)
|
[#2435](https://github.com/juanfont/headscale/pull/2435)
|
||||||
|
- Fix issue where routes passed on registration were not saved
|
||||||
|
[#2444](https://github.com/juanfont/headscale/pull/2444)
|
||||||
|
|
||||||
## 0.25.0 (2025-02-11)
|
## 0.25.0 (2025-02-11)
|
||||||
|
|
||||||
|
@ -453,6 +453,10 @@ func RegisterNode(tx *gorm.DB, node types.Node, ipv4 *netip.Addr, ipv6 *netip.Ad
|
|||||||
return nil, fmt.Errorf("failed register(save) node in the database: %w", err)
|
return nil, fmt.Errorf("failed register(save) node in the database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := SaveNodeRoutes(tx, &node); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save node routes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Caller().
|
Caller().
|
||||||
Str("node", node.Hostname).
|
Str("node", node.Hostname).
|
||||||
|
@ -744,6 +744,7 @@ func TestRenameNode(t *testing.T) {
|
|||||||
Hostname: "test",
|
Hostname: "test",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
|
Hostinfo: &tailcfg.Hostinfo{},
|
||||||
}
|
}
|
||||||
|
|
||||||
node2 := types.Node{
|
node2 := types.Node{
|
||||||
@ -753,6 +754,7 @@ func TestRenameNode(t *testing.T) {
|
|||||||
Hostname: "test",
|
Hostname: "test",
|
||||||
UserID: user2.ID,
|
UserID: user2.ID,
|
||||||
RegisterMethod: util.RegisterMethodAuthKey,
|
RegisterMethod: util.RegisterMethodAuthKey,
|
||||||
|
Hostinfo: &tailcfg.Hostinfo{},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.DB.Save(&node).Error
|
err = db.DB.Save(&node).Error
|
||||||
|
@ -17,6 +17,8 @@ import (
|
|||||||
"github.com/juanfont/headscale/integration/hsic"
|
"github.com/juanfont/headscale/integration/hsic"
|
||||||
"github.com/juanfont/headscale/integration/tsic"
|
"github.com/juanfont/headscale/integration/tsic"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/types/ipproto"
|
"tailscale.com/types/ipproto"
|
||||||
"tailscale.com/types/views"
|
"tailscale.com/types/views"
|
||||||
"tailscale.com/wgengine/filter"
|
"tailscale.com/wgengine/filter"
|
||||||
@ -1316,3 +1318,123 @@ func TestSubnetRouteACL(t *testing.T) {
|
|||||||
t.Errorf("Subnet (%s) filter, unexpected result (-want +got):\n%s", subRouter1.Hostname(), diff)
|
t.Errorf("Subnet (%s) filter, unexpected result (-want +got):\n%s", subRouter1.Hostname(), diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEnablingExitRoutes tests enabling exit routes for clients.
|
||||||
|
// Its more or less the same as TestEnablingRoutes, but with the --advertise-exit-node flag
|
||||||
|
// set during login instead of set.
|
||||||
|
func TestEnablingExitRoutes(t *testing.T) {
|
||||||
|
IntegrationSkip(t)
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
user := "user2"
|
||||||
|
|
||||||
|
scenario, err := NewScenario(dockertestMaxWait())
|
||||||
|
assertNoErrf(t, "failed to create scenario: %s", err)
|
||||||
|
defer scenario.ShutdownAssertNoPanics(t)
|
||||||
|
|
||||||
|
spec := map[string]int{
|
||||||
|
user: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{
|
||||||
|
tsic.WithExtraLoginArgs([]string{"--advertise-exit-node"}),
|
||||||
|
}, hsic.WithTestName("clienableroute"))
|
||||||
|
assertNoErrHeadscaleEnv(t, err)
|
||||||
|
|
||||||
|
allClients, err := scenario.ListTailscaleClients()
|
||||||
|
assertNoErrListClients(t, err)
|
||||||
|
|
||||||
|
err = scenario.WaitForTailscaleSync()
|
||||||
|
assertNoErrSync(t, err)
|
||||||
|
|
||||||
|
headscale, err := scenario.Headscale()
|
||||||
|
assertNoErrGetHeadscale(t, err)
|
||||||
|
|
||||||
|
err = scenario.WaitForTailscaleSync()
|
||||||
|
assertNoErrSync(t, err)
|
||||||
|
|
||||||
|
var routes []*v1.Route
|
||||||
|
err = executeAndUnmarshal(
|
||||||
|
headscale,
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"routes",
|
||||||
|
"list",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
},
|
||||||
|
&routes,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertNoErr(t, err)
|
||||||
|
assert.Len(t, routes, 4)
|
||||||
|
|
||||||
|
for _, route := range routes {
|
||||||
|
assert.True(t, route.GetAdvertised())
|
||||||
|
assert.False(t, route.GetEnabled())
|
||||||
|
assert.False(t, route.GetIsPrimary())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that no routes has been sent to the client,
|
||||||
|
// they are not yet enabled.
|
||||||
|
for _, client := range allClients {
|
||||||
|
status, err := client.Status()
|
||||||
|
assertNoErr(t, err)
|
||||||
|
|
||||||
|
for _, peerKey := range status.Peers() {
|
||||||
|
peerStatus := status.Peer[peerKey]
|
||||||
|
|
||||||
|
assert.Nil(t, peerStatus.PrimaryRoutes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable all routes
|
||||||
|
for _, route := range routes {
|
||||||
|
_, err = headscale.Execute(
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"routes",
|
||||||
|
"enable",
|
||||||
|
"--route",
|
||||||
|
strconv.Itoa(int(route.GetId())),
|
||||||
|
})
|
||||||
|
assertNoErr(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var enablingRoutes []*v1.Route
|
||||||
|
err = executeAndUnmarshal(
|
||||||
|
headscale,
|
||||||
|
[]string{
|
||||||
|
"headscale",
|
||||||
|
"routes",
|
||||||
|
"list",
|
||||||
|
"--output",
|
||||||
|
"json",
|
||||||
|
},
|
||||||
|
&enablingRoutes,
|
||||||
|
)
|
||||||
|
assertNoErr(t, err)
|
||||||
|
assert.Len(t, enablingRoutes, 4)
|
||||||
|
|
||||||
|
for _, route := range enablingRoutes {
|
||||||
|
assert.True(t, route.GetAdvertised())
|
||||||
|
assert.True(t, route.GetEnabled())
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
|
// Verify that the clients can see the new routes
|
||||||
|
for _, client := range allClients {
|
||||||
|
status, err := client.Status()
|
||||||
|
assertNoErr(t, err)
|
||||||
|
|
||||||
|
for _, peerKey := range status.Peers() {
|
||||||
|
peerStatus := status.Peer[peerKey]
|
||||||
|
|
||||||
|
require.NotNil(t, peerStatus.AllowedIPs)
|
||||||
|
assert.Len(t, peerStatus.AllowedIPs.AsSlice(), 4)
|
||||||
|
assert.Contains(t, peerStatus.AllowedIPs.AsSlice(), tsaddr.AllIPv4())
|
||||||
|
assert.Contains(t, peerStatus.AllowedIPs.AsSlice(), tsaddr.AllIPv6())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -80,6 +80,7 @@ type TailscaleInContainer struct {
|
|||||||
withExtraHosts []string
|
withExtraHosts []string
|
||||||
workdir string
|
workdir string
|
||||||
netfilter string
|
netfilter string
|
||||||
|
extraLoginArgs []string
|
||||||
|
|
||||||
// build options, solely for HEAD
|
// build options, solely for HEAD
|
||||||
buildConfig TailscaleInContainerBuildConfig
|
buildConfig TailscaleInContainerBuildConfig
|
||||||
@ -203,6 +204,14 @@ func WithBuildTag(tag string) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithExtraLoginArgs adds additional arguments to the `tailscale up` command
|
||||||
|
// as part of the Login function.
|
||||||
|
func WithExtraLoginArgs(args []string) Option {
|
||||||
|
return func(tsic *TailscaleInContainer) {
|
||||||
|
tsic.extraLoginArgs = args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// New returns a new TailscaleInContainer instance.
|
// New returns a new TailscaleInContainer instance.
|
||||||
func New(
|
func New(
|
||||||
pool *dockertest.Pool,
|
pool *dockertest.Pool,
|
||||||
@ -436,6 +445,10 @@ func (t *TailscaleInContainer) Login(
|
|||||||
"--accept-routes=false",
|
"--accept-routes=false",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.extraLoginArgs != nil {
|
||||||
|
command = append(command, t.extraLoginArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
if t.withSSH {
|
if t.withSSH {
|
||||||
command = append(command, "--ssh")
|
command = append(command, "--ssh")
|
||||||
}
|
}
|
||||||
@ -475,6 +488,10 @@ func (t *TailscaleInContainer) LoginWithURL(
|
|||||||
"--accept-routes=false",
|
"--accept-routes=false",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.extraLoginArgs != nil {
|
||||||
|
command = append(command, t.extraLoginArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
stdout, stderr, err := t.Execute(command)
|
stdout, stderr, err := t.Execute(command)
|
||||||
if errors.Is(err, errTailscaleNotLoggedIn) {
|
if errors.Is(err, errTailscaleNotLoggedIn) {
|
||||||
return nil, errTailscaleCannotUpWithoutAuthkey
|
return nil, errTailscaleCannotUpWithoutAuthkey
|
||||||
|
Loading…
Reference in New Issue
Block a user