diff --git a/CHANGELOG.md b/CHANGELOG.md index a57e7c07..6f68b47d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Relax username validation to allow emails [#2364](https://github.com/juanfont/headscale/pull/2364) +- Remove invalid routes and add stronger constraints for routes to avoid API panic + [#2371](https://github.com/juanfont/headscale/pull/2371) ## 0.24.0 (2025-01-17) diff --git a/cmd/headscale/cli/routes.go b/cmd/headscale/cli/routes.go index e39b407f..ef289497 100644 --- a/cmd/headscale/cli/routes.go +++ b/cmd/headscale/cli/routes.go @@ -251,10 +251,15 @@ func routesToPtables(routes []*v1.Route) pterm.TableData { isPrimaryStr = strconv.FormatBool(route.GetIsPrimary()) } + var nodeName string + if route.GetNode() != nil { + nodeName = route.GetNode().GetGivenName() + } + tableData = append(tableData, []string{ strconv.FormatUint(route.GetId(), Base10), - route.GetNode().GetGivenName(), + nodeName, route.GetPrefix(), strconv.FormatBool(route.GetAdvertised()), strconv.FormatBool(route.GetEnabled()), diff --git a/hscontrol/db/db.go b/hscontrol/db/db.go index 0d9120c2..553d7f0e 100644 --- a/hscontrol/db/db.go +++ b/hscontrol/db/db.go @@ -521,6 +521,27 @@ func NewHeadscaleDatabase( }, Rollback: func(db *gorm.DB) error { return nil }, }, + { + // Add a constraint to routes ensuring they cannot exist without a node. + ID: "202501221827", + Migrate: func(tx *gorm.DB) error { + // Remove any invalid routes associated with a node that does not exist. + if tx.Migrator().HasTable(&types.Route{}) && tx.Migrator().HasTable(&types.Node{}) { + err := tx.Exec("delete from routes where node_id not in (select id from nodes)").Error + if err != nil { + return err + } + } + + err := tx.AutoMigrate(&types.Route{}) + if err != nil { + return err + } + + return nil + }, + Rollback: func(db *gorm.DB) error { return nil }, + }, }, ) diff --git a/hscontrol/types/routes.go b/hscontrol/types/routes.go index 4ef3621f..12559fa6 100644 --- a/hscontrol/types/routes.go +++ b/hscontrol/types/routes.go @@ -13,7 +13,7 @@ import ( type Route struct { gorm.Model - NodeID uint64 + NodeID uint64 `gorm:"not null"` Node *Node // TODO(kradalby): change this custom type to netip.Prefix @@ -79,7 +79,6 @@ func (rs Routes) Proto() []*v1.Route { for _, route := range rs { protoRoute := v1.Route{ Id: uint64(route.ID), - Node: route.Node.Proto(), Prefix: route.Prefix.String(), Advertised: route.Advertised, Enabled: route.Enabled, @@ -88,6 +87,10 @@ func (rs Routes) Proto() []*v1.Route { UpdatedAt: timestamppb.New(route.UpdatedAt), } + if route.Node != nil { + protoRoute.Node = route.Node.Proto() + } + if route.DeletedAt.Valid { protoRoute.DeletedAt = timestamppb.New(route.DeletedAt.Time) }