From 5a4e52b727d402d78a5f3c5ef6b74dcb3b448fe2 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Mon, 27 May 2024 11:53:37 +0100 Subject: [PATCH 01/12] remove last_successful_update error check (#1959) most of the time we dont even check this error and checking the string for particular errors is very flake as different databases (sqlite and psql) use different error messages, and some users might have it in other languages. Fixes #1956 Signed-off-by: Kristoffer Dalby --- hscontrol/db/db.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/hscontrol/db/db.go b/hscontrol/db/db.go index a30939c1..b87d6da6 100644 --- a/hscontrol/db/db.go +++ b/hscontrol/db/db.go @@ -92,7 +92,7 @@ func NewHeadscaleDatabase( RenameColumn(&types.Node{}, "nickname", "given_name") dbConn.Model(&types.Node{}).Where("auth_key_id = ?", 0).Update("auth_key_id", nil) - // If the Node table has a column for registered, + // If the Node table has a column for registered, // find all occourences of "false" and drop them. Then // remove the column. if tx.Migrator().HasColumn(&types.Node{}, "registered") { @@ -319,14 +319,8 @@ func NewHeadscaleDatabase( // no longer used. ID: "202402151347", Migrate: func(tx *gorm.DB) error { - err := tx.Migrator().DropColumn(&types.Node{}, "last_successful_update") - if err != nil && strings.Contains(err.Error(), `of relation "nodes" does not exist`) { - return nil - } else { - return err - } - - return err + _ = tx.Migrator().DropColumn(&types.Node{}, "last_successful_update") + return nil }, Rollback: func(tx *gorm.DB) error { return nil From 5f9c26930ce796284a8fc7ad167f76e187813d64 Mon Sep 17 00:00:00 2001 From: Dongjun Na Date: Wed, 29 May 2024 01:11:39 +0900 Subject: [PATCH 02/12] fixed typo and path (#1960) --- docs/index.md | 2 +- docs/running-headscale-linux-manual.md | 2 +- docs/running-headscale-openbsd.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index d13339d8..f0b8bb00 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,7 @@ hide: `headscale` is an open source, self-hosted implementation of the Tailscale control server. -This page contains the documentation for the latest version of headscale. Please also check our [FAQ](/faq/). +This page contains the documentation for the latest version of headscale. Please also check our [FAQ](faq.md). Join our [Discord](https://discord.gg/c84AZQhmpx) server for a chat and community support. diff --git a/docs/running-headscale-linux-manual.md b/docs/running-headscale-linux-manual.md index 4108208f..3651c892 100644 --- a/docs/running-headscale-linux-manual.md +++ b/docs/running-headscale-linux-manual.md @@ -57,7 +57,7 @@ describing how to make `headscale` run properly in a server environment. touch /etc/headscale/config.yaml ``` - **(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository. + **(Strongly Recommended)** Download a copy of the [example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository. 1. Start the headscale server: diff --git a/docs/running-headscale-openbsd.md b/docs/running-headscale-openbsd.md index e1d8d83f..72c7bf79 100644 --- a/docs/running-headscale-openbsd.md +++ b/docs/running-headscale-openbsd.md @@ -93,7 +93,7 @@ describing how to make `headscale` run properly in a server environment. touch /etc/headscale/config.yaml ``` -**(Strongly Recommended)** Download a copy of the [example configuration][config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository. +**(Strongly Recommended)** Download a copy of the [example configuration](https://github.com/juanfont/headscale/blob/main/config-example.yaml) from the headscale repository. 1. Start the headscale server: From 1f4b59566a27d5933f32ae58d0fcc71ba8f1ea9a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Jun 2024 07:23:16 +0000 Subject: [PATCH 03/12] flake.lock: Update (#1958) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 351c657c..060a290a 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1716062047, - "narHash": "sha256-OhysviwHQz4p2HZL4g7XGMLoUbWMjkMr/ogaR3VUYNA=", + "lastModified": 1717774105, + "narHash": "sha256-HV97wqUQv9wvptiHCb3Y0/YH0lJ60uZ8FYfEOIzYEqI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "02923630b89aa1ab36ef8e422501a6f4fd4b2016", + "rev": "d226935fd75012939397c83f6c385e4d6d832288", "type": "github" }, "original": { From 51676c668bbe1bd36747b4e8f3b54ca58ef8c2b8 Mon Sep 17 00:00:00 2001 From: Lars Kiesow Date: Sat, 15 Jun 2024 09:40:49 +0200 Subject: [PATCH 04/12] Make registration screen easier to use (#1975) --- CHANGELOG.md | 1 + hscontrol/handlers.go | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03516fd6..dce08f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/ - Log available update as warning [#1877](https://github.com/juanfont/headscale/pull/1877) - Add `autogroup:internet` to Policy [#1917](https://github.com/juanfont/headscale/pull/1917) - Restore foreign keys and add constraints [#1562](https://github.com/juanfont/headscale/pull/1562) +- Make registration page easier to use on mobile devices ## 0.22.3 (2023-05-12) diff --git a/hscontrol/handlers.go b/hscontrol/handlers.go index a6bbd1b8..6efe1984 100644 --- a/hscontrol/handlers.go +++ b/hscontrol/handlers.go @@ -143,6 +143,18 @@ var registerWebAPITemplate = template.Must( Registration - Headscale + +

headscale

@@ -150,7 +162,7 @@ var registerWebAPITemplate = template.Must(

Run the command below in the headscale server to add this machine to your network:

-
headscale nodes register --user USERNAME --key {{.Key}}
+ headscale nodes register --user USERNAME --key {{.Key}} `)) From dfc089ed6a5c116f3c8dff17f97a68822468fd2c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 16 Jun 2024 20:24:08 +0000 Subject: [PATCH 05/12] flake.lock: Update (#1979) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 060a290a..51019abd 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1717774105, - "narHash": "sha256-HV97wqUQv9wvptiHCb3Y0/YH0lJ60uZ8FYfEOIzYEqI=", + "lastModified": 1718276985, + "narHash": "sha256-u1fA0DYQYdeG+5kDm1bOoGcHtX0rtC7qs2YA2N1X++I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d226935fd75012939397c83f6c385e4d6d832288", + "rev": "3f84a279f1a6290ce154c5531378acc827836fbb", "type": "github" }, "original": { From 99e91a9d8a740df793b54f4070de1739d894bf53 Mon Sep 17 00:00:00 2001 From: Kyhwana Pardus Date: Sun, 23 Jun 2024 10:47:26 +1200 Subject: [PATCH 06/12] Update reverse-proxy.md (#1986) Add blurb about how cloudflare proxy/tunnels is not supported/will not work --- docs/reverse-proxy.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reverse-proxy.md b/docs/reverse-proxy.md index c6fd4b16..23c61c26 100644 --- a/docs/reverse-proxy.md +++ b/docs/reverse-proxy.md @@ -15,6 +15,10 @@ The reverse proxy MUST be configured to support WebSockets, as it is needed for WebSockets support is required when using the headscale embedded DERP server. In this case, you will also need to expose the UDP port used for STUN (by default, udp/3478). Please check our [config-example.yaml](https://github.com/juanfont/headscale/blob/main/config-example.yaml). +### Cloudflare + +Running headscale behind a cloudflare proxy or cloudflare tunnel is not supported and will not work as Cloudflare does not support WebSocket POSTs as required by the Tailscale protocol. See [this issue](https://github.com/juanfont/headscale/issues/1468) + ### TLS Headscale can be configured not to use TLS, leaving it to the reverse proxy to handle. Add the following configuration values to your headscale config file. From 69c33658f63fd5af1c19ba176afaa31993aa83e4 Mon Sep 17 00:00:00 2001 From: Lars Kiesow Date: Sun, 23 Jun 2024 00:52:23 +0200 Subject: [PATCH 07/12] Fix android docs (#1976) The current Tailscale app for Android looks and behaves differently. This patch updates the documentation for that. --- docs/android-client.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/android-client.md b/docs/android-client.md index d4f8129c..21dd8d21 100644 --- a/docs/android-client.md +++ b/docs/android-client.md @@ -12,8 +12,8 @@ Ensure that the installed version is at least 1.30.0, as that is the first relea ## Configuring the headscale URL -After opening the app, the kebab menu icon (three dots) on the top bar on the right must be repeatedly opened and closed until the _Change server_ option appears in the menu. This is where you can enter your headscale URL. +After opening the app: -A screen recording of this process can be seen in the `tailscale-android` PR which implemented this functionality: - -After saving and restarting the app, selecting the regular _Sign in_ option (non-SSO) should open up the headscale authentication page. +- Open setting and go into account settings +- In the kebab menu icon (three dots) on the top bar on the right select “Use an alternate server” +- Enter your server URL and follow the instructions From 8f8f469c0ac2ecbdfce79f14f055cbf1e1ff444d Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 23 Jun 2024 22:06:50 +0200 Subject: [PATCH 08/12] Remove allocations of lists before use (#1989) * policy: remove allocs before appends in acls Signed-off-by: Kristoffer Dalby * notifier: make batcher tests stable/non-flaky Signed-off-by: Kristoffer Dalby * {db,derp,mapper}: dont allocate until append Signed-off-by: Kristoffer Dalby --------- Signed-off-by: Kristoffer Dalby --- hscontrol/db/node.go | 6 ++--- hscontrol/db/routes.go | 4 +-- hscontrol/derp/derp.go | 2 +- hscontrol/mapper/mapper.go | 2 +- hscontrol/notifier/notifier_test.go | 16 ++++++++++++ hscontrol/policy/acls.go | 40 ++++++++++++++--------------- hscontrol/policy/acls_test.go | 8 +++--- 7 files changed, 46 insertions(+), 32 deletions(-) diff --git a/hscontrol/db/node.go b/hscontrol/db/node.go index c675dc7c..e36d6ed1 100644 --- a/hscontrol/db/node.go +++ b/hscontrol/db/node.go @@ -215,7 +215,7 @@ func SetTags( return nil } - newTags := types.StringList{} + var newTags types.StringList for _, tag := range tags { if !util.StringOrPrefixListContains(newTags, tag) { newTags = append(newTags, tag) @@ -452,7 +452,7 @@ func GetAdvertisedRoutes(tx *gorm.DB, node *types.Node) ([]netip.Prefix, error) return nil, fmt.Errorf("getting advertised routes for node(%d): %w", node.ID, err) } - prefixes := []netip.Prefix{} + var prefixes []netip.Prefix for _, route := range routes { prefixes = append(prefixes, netip.Prefix(route.Prefix)) } @@ -478,7 +478,7 @@ func GetEnabledRoutes(tx *gorm.DB, node *types.Node) ([]netip.Prefix, error) { return nil, fmt.Errorf("getting enabled routes for node(%d): %w", node.ID, err) } - prefixes := []netip.Prefix{} + var prefixes []netip.Prefix for _, route := range routes { prefixes = append(prefixes, netip.Prefix(route.Prefix)) } diff --git a/hscontrol/db/routes.go b/hscontrol/db/routes.go index 74b2b4b7..3b897190 100644 --- a/hscontrol/db/routes.go +++ b/hscontrol/db/routes.go @@ -222,7 +222,7 @@ func DeleteRoute( return nil, err } - routesToDelete := types.Routes{} + var routesToDelete types.Routes for _, r := range routes { if r.IsExitRoute() { routesToDelete = append(routesToDelete, r) @@ -623,7 +623,7 @@ func EnableAutoApprovedRoutes( log.Trace().Interface("routes", routes).Msg("routes for autoapproving") - approvedRoutes := types.Routes{} + var approvedRoutes types.Routes for _, advertisedRoute := range routes { if advertisedRoute.Enabled { diff --git a/hscontrol/derp/derp.go b/hscontrol/derp/derp.go index 80ec520d..3afcb4ea 100644 --- a/hscontrol/derp/derp.go +++ b/hscontrol/derp/derp.go @@ -81,7 +81,7 @@ func mergeDERPMaps(derpMaps []*tailcfg.DERPMap) *tailcfg.DERPMap { } func GetDERPMap(cfg types.DERPConfig) *tailcfg.DERPMap { - derpMaps := make([]*tailcfg.DERPMap, 0) + var derpMaps []*tailcfg.DERPMap for _, path := range cfg.Paths { log.Debug(). diff --git a/hscontrol/mapper/mapper.go b/hscontrol/mapper/mapper.go index d4f4392a..a6fa9ad6 100644 --- a/hscontrol/mapper/mapper.go +++ b/hscontrol/mapper/mapper.go @@ -102,7 +102,7 @@ func generateUserProfiles( userMap[peer.User.Name] = peer.User // not worth checking if already is there } - profiles := []tailcfg.UserProfile{} + var profiles []tailcfg.UserProfile for _, user := range userMap { displayName := user.Name diff --git a/hscontrol/notifier/notifier_test.go b/hscontrol/notifier/notifier_test.go index 8841a46d..c41e0039 100644 --- a/hscontrol/notifier/notifier_test.go +++ b/hscontrol/notifier/notifier_test.go @@ -3,6 +3,7 @@ package notifier import ( "context" "net/netip" + "sort" "testing" "time" @@ -221,6 +222,11 @@ func TestBatcher(t *testing.T) { // We will call flush manually for the tests, // so do not run the worker. BatchChangeDelay: time.Hour, + + // Since we do not load the config, we wont get the + // default, so set it manually so we dont time out + // and have flakes. + NotifierSendTimeout: time.Second, }, }) @@ -241,6 +247,16 @@ func TestBatcher(t *testing.T) { got = append(got, out) } + // Make the inner order stable for comparison. + for _, u := range got { + sort.Slice(u.ChangeNodes, func(i, j int) bool { + return u.ChangeNodes[i] < u.ChangeNodes[j] + }) + sort.Slice(u.ChangePatches, func(i, j int) bool { + return u.ChangePatches[i].NodeID < u.ChangePatches[j].NodeID + }) + } + if diff := cmp.Diff(tt.want, got, util.Comparers...); diff != "" { t.Errorf("batcher() unexpected result (-want +got):\n%s", diff) } diff --git a/hscontrol/policy/acls.go b/hscontrol/policy/acls.go index 1196995d..9dde401b 100644 --- a/hscontrol/policy/acls.go +++ b/hscontrol/policy/acls.go @@ -180,14 +180,14 @@ func (pol *ACLPolicy) CompileFilterRules( return tailcfg.FilterAllowAll, nil } - rules := []tailcfg.FilterRule{} + var rules []tailcfg.FilterRule for index, acl := range pol.ACLs { if acl.Action != "accept" { return nil, ErrInvalidAction } - srcIPs := []string{} + var srcIPs []string for srcIndex, src := range acl.Sources { srcs, err := pol.expandSource(src, nodes) if err != nil { @@ -221,7 +221,7 @@ func (pol *ACLPolicy) CompileFilterRules( return nil, err } - dests := []tailcfg.NetPortRange{} + var dests []tailcfg.NetPortRange for _, dest := range expanded.Prefixes() { for _, port := range *ports { pr := tailcfg.NetPortRange{ @@ -251,8 +251,7 @@ func ReduceFilterRules(node *types.Node, rules []tailcfg.FilterRule) []tailcfg.F for _, rule := range rules { // record if the rule is actually relevant for the given node. - dests := []tailcfg.NetPortRange{} - + var dests []tailcfg.NetPortRange DEST_LOOP: for _, dest := range rule.DstPorts { expanded, err := util.ParseIPSet(dest.IP, nil) @@ -301,7 +300,7 @@ func (pol *ACLPolicy) CompileSSHPolicy( return nil, nil } - rules := []*tailcfg.SSHRule{} + var rules []*tailcfg.SSHRule acceptAction := tailcfg.SSHAction{ Message: "", @@ -533,8 +532,7 @@ func (pol *ACLPolicy) expandSource( return []string{}, err } - prefixes := []string{} - + var prefixes []string for _, prefix := range ipSet.Prefixes() { prefixes = append(prefixes, prefix.String()) } @@ -615,8 +613,8 @@ func excludeCorrectlyTaggedNodes( nodes types.Nodes, user string, ) types.Nodes { - out := types.Nodes{} - tags := []string{} + var out types.Nodes + var tags []string for tag := range aclPolicy.TagOwners { owners, _ := expandOwnersFromTag(aclPolicy, user) ns := append(owners, user) @@ -661,7 +659,7 @@ func expandPorts(portsStr string, isWild bool) (*[]tailcfg.PortRange, error) { return nil, ErrWildcardIsNeeded } - ports := []tailcfg.PortRange{} + var ports []tailcfg.PortRange for _, portStr := range strings.Split(portsStr, ",") { log.Trace().Msgf("parsing portstring: %s", portStr) rang := strings.Split(portStr, "-") @@ -737,7 +735,7 @@ func expandOwnersFromTag( func (pol *ACLPolicy) expandUsersFromGroup( group string, ) ([]string, error) { - users := []string{} + var users []string log.Trace().Caller().Interface("pol", pol).Msg("test") aclGroups, ok := pol.Groups[group] if !ok { @@ -772,7 +770,7 @@ func (pol *ACLPolicy) expandIPsFromGroup( group string, nodes types.Nodes, ) (*netipx.IPSet, error) { - build := netipx.IPSetBuilder{} + var build netipx.IPSetBuilder users, err := pol.expandUsersFromGroup(group) if err != nil { @@ -792,7 +790,7 @@ func (pol *ACLPolicy) expandIPsFromTag( alias string, nodes types.Nodes, ) (*netipx.IPSet, error) { - build := netipx.IPSetBuilder{} + var build netipx.IPSetBuilder // check for forced tags for _, node := range nodes { @@ -841,7 +839,7 @@ func (pol *ACLPolicy) expandIPsFromUser( user string, nodes types.Nodes, ) (*netipx.IPSet, error) { - build := netipx.IPSetBuilder{} + var build netipx.IPSetBuilder filteredNodes := filterNodesByUser(nodes, user) filteredNodes = excludeCorrectlyTaggedNodes(pol, filteredNodes, user) @@ -866,7 +864,7 @@ func (pol *ACLPolicy) expandIPsFromSingleIP( matches := nodes.FilterByIP(ip) - build := netipx.IPSetBuilder{} + var build netipx.IPSetBuilder build.Add(ip) for _, node := range matches { @@ -881,7 +879,7 @@ func (pol *ACLPolicy) expandIPsFromIPPrefix( nodes types.Nodes, ) (*netipx.IPSet, error) { log.Trace().Str("prefix", prefix.String()).Msg("expandAlias got prefix") - build := netipx.IPSetBuilder{} + var build netipx.IPSetBuilder build.AddPrefix(prefix) // This is suboptimal and quite expensive, but if we only add the prefix, we will miss all the relevant IPv6 @@ -931,8 +929,8 @@ func isAutoGroup(str string) bool { func (pol *ACLPolicy) TagsOfNode( node *types.Node, ) ([]string, []string) { - validTags := make([]string, 0) - invalidTags := make([]string, 0) + var validTags []string + var invalidTags []string // TODO(kradalby): Why is this sometimes nil? coming from tailNode? if node == nil { @@ -973,7 +971,7 @@ func (pol *ACLPolicy) TagsOfNode( } func filterNodesByUser(nodes types.Nodes, user string) types.Nodes { - out := types.Nodes{} + var out types.Nodes for _, node := range nodes { if node.User.Name == user { out = append(out, node) @@ -989,7 +987,7 @@ func FilterNodesByACL( nodes types.Nodes, filter []tailcfg.FilterRule, ) types.Nodes { - result := types.Nodes{} + var result types.Nodes for index, peer := range nodes { if peer.ID == node.ID { diff --git a/hscontrol/policy/acls_test.go b/hscontrol/policy/acls_test.go index b0cafe10..c1e7ae08 100644 --- a/hscontrol/policy/acls_test.go +++ b/hscontrol/policy/acls_test.go @@ -943,7 +943,7 @@ func Test_listNodesInUser(t *testing.T) { }, user: "mickael", }, - want: types.Nodes{}, + want: nil, }, } for _, test := range tests { @@ -1645,7 +1645,7 @@ func TestACLPolicy_generateFilterRules(t *testing.T) { name: "no-policy", field: field{}, args: args{}, - want: []tailcfg.FilterRule{}, + want: nil, wantErr: false, }, { @@ -2896,7 +2896,7 @@ func Test_getFilteredByACLPeers(t *testing.T) { User: types.User{Name: "marc"}, }, }, - want: types.Nodes{}, + want: nil, }, { // Investigating 699 @@ -3426,7 +3426,7 @@ func TestSSHRules(t *testing.T) { }, }, }, - want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{}}, + want: &tailcfg.SSHPolicy{Rules: nil}, }, } From 4a34cfc4a6fbdbde818647b818dc171512fa1253 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 23 Jun 2024 22:06:59 +0200 Subject: [PATCH 09/12] Make write-ahead-log default and configurable for sqlite (#1985) * correctly enable WAL log for sqlite this commit makes headscale correctly enable write-ahead-log for sqlite and adds an option to turn it on and off. WAL is enabled by default and should make sqlite perform a lot better, even further eliminating the need to use postgres. It also adds a couple of other useful defaults. Signed-off-by: Kristoffer Dalby * update changelog Signed-off-by: Kristoffer Dalby --------- Signed-off-by: Kristoffer Dalby --- CHANGELOG.md | 1 + config-example.yaml | 4 ++++ hscontrol/db/db.go | 20 ++++++++++++++++++-- hscontrol/types/config.go | 6 +++++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dce08f68..666e1670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ after improving the test harness as part of adopting [#1460](https://github.com/ - Add `autogroup:internet` to Policy [#1917](https://github.com/juanfont/headscale/pull/1917) - Restore foreign keys and add constraints [#1562](https://github.com/juanfont/headscale/pull/1562) - Make registration page easier to use on mobile devices +- Make write-ahead-log default on and configurable for SQLite [#1985](https://github.com/juanfont/headscale/pull/1985) ## 0.22.3 (2023-05-12) diff --git a/config-example.yaml b/config-example.yaml index 867f8903..f1bc1631 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -144,6 +144,10 @@ database: sqlite: path: /var/lib/headscale/db.sqlite + # Enable WAL mode for SQLite. This is recommended for production environments. + # https://www.sqlite.org/wal.html + write_ahead_log: true + # # Postgres config # postgres: # # If using a Unix socket to connect to Postgres, set the socket path in the 'host' field and leave 'port' blank. diff --git a/hscontrol/db/db.go b/hscontrol/db/db.go index b87d6da6..69994d02 100644 --- a/hscontrol/db/db.go +++ b/hscontrol/db/db.go @@ -434,13 +434,29 @@ func openDB(cfg types.DatabaseConfig) (*gorm.DB, error) { Msg("Opening database") db, err := gorm.Open( - sqlite.Open(cfg.Sqlite.Path+"?_synchronous=1&_journal_mode=WAL"), + sqlite.Open(cfg.Sqlite.Path), &gorm.Config{ Logger: dbLogger, }, ) - db.Exec("PRAGMA foreign_keys=ON") + if err := db.Exec(` + PRAGMA foreign_keys=ON; + PRAGMA busy_timeout=10000; + PRAGMA auto_vacuum=INCREMENTAL; + PRAGMA synchronous=NORMAL; + `).Error; err != nil { + return nil, fmt.Errorf("enabling foreign keys: %w", err) + } + + if cfg.Sqlite.WriteAheadLog { + if err := db.Exec(` + PRAGMA journal_mode=WAL; + PRAGMA wal_autocheckpoint=0; + `).Error; err != nil { + return nil, fmt.Errorf("setting WAL mode: %w", err) + } + } // The pure Go SQLite library does not handle locking in // the same way as the C based one and we cant use the gorm diff --git a/hscontrol/types/config.go b/hscontrol/types/config.go index ab17cfb0..00934af6 100644 --- a/hscontrol/types/config.go +++ b/hscontrol/types/config.go @@ -81,7 +81,8 @@ type Config struct { } type SqliteConfig struct { - Path string + Path string + WriteAheadLog bool } type PostgresConfig struct { @@ -222,6 +223,8 @@ func LoadConfig(path string, isFile bool) error { viper.SetDefault("database.postgres.max_idle_conns", 10) viper.SetDefault("database.postgres.conn_max_idle_time_secs", 3600) + viper.SetDefault("database.sqlite.write_ahead_log", true) + viper.SetDefault("oidc.scope", []string{oidc.ScopeOpenID, "profile", "email"}) viper.SetDefault("oidc.strip_email_domain", true) viper.SetDefault("oidc.only_start_if_oidc_is_available", true) @@ -443,6 +446,7 @@ func GetDatabaseConfig() DatabaseConfig { Path: util.AbsolutePathFromConfigPath( viper.GetString("database.sqlite.path"), ), + WriteAheadLog: viper.GetBool("database.sqlite.write_ahead_log"), }, Postgres: PostgresConfig{ Host: viper.GetString("database.postgres.host"), From 14a3f94f0cab3f88322350bc060f28c406754821 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 26 Jun 2024 13:44:40 +0200 Subject: [PATCH 10/12] fix search domains and remove username from magicdns (#1987) --- CHANGELOG.md | 4 ++ config-example.yaml | 9 +++ hscontrol/mapper/mapper.go | 52 ++++++++------- hscontrol/mapper/mapper_test.go | 11 +-- hscontrol/mapper/tail.go | 2 +- hscontrol/types/config.go | 29 ++++---- hscontrol/types/node.go | 25 ++++--- hscontrol/types/node_test.go | 114 ++++++++++++++++++++++++++++---- 8 files changed, 183 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 666e1670..fced0b6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,10 @@ after improving the test harness as part of adopting [#1460](https://github.com/ - Prefixes are now defined per v4 and v6 range. [#1756](https://github.com/juanfont/headscale/pull/1756) - `ip_prefixes` option is now `prefixes.v4` and `prefixes.v6` - `prefixes.allocation` can be set to assign IPs at `sequential` or `random`. [#1869](https://github.com/juanfont/headscale/pull/1869) +- MagicDNS domains no longer contain usernames []() + - This is in preperation to fix Headscales implementation of tags which currently does not correctly remove the link between a tagged device and a user. As tagged devices will not have a user, this will require a change to the DNS generation, removing the username, see [#1369](https://github.com/juanfont/headscale/issues/1369) for more information. + - `use_username_in_magic_dns` can be used to turn this behaviour on again, but note that this option _will be removed_ when tags are fixed. + - This option brings Headscales behaviour in line with Tailscale. ### Changes diff --git a/config-example.yaml b/config-example.yaml index f1bc1631..4608317a 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -265,6 +265,15 @@ dns_config: # Only works if there is at least a nameserver defined. magic_dns: true + # DEPRECATED + # Use the username as part of the DNS name for nodes, with this option enabled: + # node1.username.example.com + # while when this is disabled: + # node1.example.com + # This is a legacy option as Headscale has have this wrongly implemented + # while in upstream Tailscale, the username is not included. + use_username_in_magic_dns: false + # Defines the base domain to create the hostnames for MagicDNS. # `base_domain` must be a FQDNs, without the trailing dot. # The FQDN of the hosts will be diff --git a/hscontrol/mapper/mapper.go b/hscontrol/mapper/mapper.go index a6fa9ad6..adc49669 100644 --- a/hscontrol/mapper/mapper.go +++ b/hscontrol/mapper/mapper.go @@ -122,37 +122,41 @@ func generateUserProfiles( } func generateDNSConfig( - base *tailcfg.DNSConfig, + cfg *types.Config, baseDomain string, node *types.Node, peers types.Nodes, ) *tailcfg.DNSConfig { - dnsConfig := base.Clone() + if cfg.DNSConfig == nil { + return nil + } + + dnsConfig := cfg.DNSConfig.Clone() // if MagicDNS is enabled - if base != nil && base.Proxied { - // Only inject the Search Domain of the current user - // shared nodes should use their full FQDN - dnsConfig.Domains = append( - dnsConfig.Domains, - fmt.Sprintf( - "%s.%s", - node.User.Name, - baseDomain, - ), - ) + if dnsConfig.Proxied { + if cfg.DNSUserNameInMagicDNS { + // Only inject the Search Domain of the current user + // shared nodes should use their full FQDN + dnsConfig.Domains = append( + dnsConfig.Domains, + fmt.Sprintf( + "%s.%s", + node.User.Name, + baseDomain, + ), + ) - userSet := mapset.NewSet[types.User]() - userSet.Add(node.User) - for _, p := range peers { - userSet.Add(p.User) + userSet := mapset.NewSet[types.User]() + userSet.Add(node.User) + for _, p := range peers { + userSet.Add(p.User) + } + for _, user := range userSet.ToSlice() { + dnsRoute := fmt.Sprintf("%v.%v", user.Name, baseDomain) + dnsConfig.Routes[dnsRoute] = nil + } } - for _, user := range userSet.ToSlice() { - dnsRoute := fmt.Sprintf("%v.%v", user.Name, baseDomain) - dnsConfig.Routes[dnsRoute] = nil - } - } else { - dnsConfig = base } addNextDNSMetadata(dnsConfig.Resolvers, node) @@ -568,7 +572,7 @@ func appendPeerChanges( profiles := generateUserProfiles(node, changed, cfg.BaseDomain) dnsConfig := generateDNSConfig( - cfg.DNSConfig, + cfg, cfg.BaseDomain, node, peers, diff --git a/hscontrol/mapper/mapper_test.go b/hscontrol/mapper/mapper_test.go index 2ba3d031..be48c6fa 100644 --- a/hscontrol/mapper/mapper_test.go +++ b/hscontrol/mapper/mapper_test.go @@ -127,7 +127,10 @@ func TestDNSConfigMapResponse(t *testing.T) { } got := generateDNSConfig( - &dnsConfigOrig, + &types.Config{ + DNSConfig: &dnsConfigOrig, + DNSUserNameInMagicDNS: true, + }, baseDomain, nodeInShared1, peersOfNodeInShared1, @@ -187,9 +190,9 @@ func Test_fullMapResponse(t *testing.T) { UserID: 0, User: types.User{Name: "mini"}, ForcedTags: []string{}, - AuthKey: &types.PreAuthKey{}, - LastSeen: &lastSeen, - Expiry: &expire, + AuthKey: &types.PreAuthKey{}, + LastSeen: &lastSeen, + Expiry: &expire, Hostinfo: &tailcfg.Hostinfo{}, Routes: []types.Route{ { diff --git a/hscontrol/mapper/tail.go b/hscontrol/mapper/tail.go index ac39d35e..92fbed81 100644 --- a/hscontrol/mapper/tail.go +++ b/hscontrol/mapper/tail.go @@ -77,7 +77,7 @@ func tailNode( keyExpiry = time.Time{} } - hostname, err := node.GetFQDN(cfg.DNSConfig, cfg.BaseDomain) + hostname, err := node.GetFQDN(cfg, cfg.BaseDomain) if err != nil { return nil, fmt.Errorf("tailNode, failed to create FQDN: %s", err) } diff --git a/hscontrol/types/config.go b/hscontrol/types/config.go index 00934af6..8ac8dcc4 100644 --- a/hscontrol/types/config.go +++ b/hscontrol/types/config.go @@ -63,7 +63,8 @@ type Config struct { ACMEURL string ACMEEmail string - DNSConfig *tailcfg.DNSConfig + DNSConfig *tailcfg.DNSConfig + DNSUserNameInMagicDNS bool UnixSocket string UnixSocketPermission fs.FileMode @@ -204,6 +205,7 @@ func LoadConfig(path string, isFile bool) error { viper.SetDefault("dns_config", nil) viper.SetDefault("dns_config.override_local_dns", true) + viper.SetDefault("dns_config.use_username_in_magic_dns", false) viper.SetDefault("derp.server.enabled", false) viper.SetDefault("derp.server.stun.enabled", true) @@ -540,16 +542,6 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) { dnsConfig.Domains = domains } - if viper.IsSet("dns_config.domains") { - domains := viper.GetStringSlice("dns_config.domains") - if len(dnsConfig.Resolvers) > 0 { - dnsConfig.Domains = domains - } else if domains != nil { - log.Warn(). - Msg("Warning: dns_config.domains is set, but no nameservers are configured. Ignoring domains.") - } - } - if viper.IsSet("dns_config.extra_records") { var extraRecords []tailcfg.DNSRecord @@ -575,8 +567,18 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) { baseDomain = "headscale.net" // does not really matter when MagicDNS is not enabled } - log.Trace().Interface("dns_config", dnsConfig).Msg("DNS configuration loaded") + if !viper.GetBool("dns_config.use_username_in_magic_dns") { + dnsConfig.Domains = []string{baseDomain} + } else { + log.Warn().Msg("DNS: Usernames in DNS has been deprecated, this option will be remove in future versions") + log.Warn().Msg("DNS: see 0.23.0 changelog for more information.") + } + if domains := viper.GetStringSlice("dns_config.domains"); len(domains) > 0 { + dnsConfig.Domains = append(dnsConfig.Domains, domains...) + } + + log.Trace().Interface("dns_config", dnsConfig).Msg("DNS configuration loaded") return dnsConfig, baseDomain } @@ -719,7 +721,8 @@ func GetHeadscaleConfig() (*Config, error) { TLS: GetTLSConfig(), - DNSConfig: dnsConfig, + DNSConfig: dnsConfig, + DNSUserNameInMagicDNS: viper.GetBool("dns_config.use_username_in_magic_dns"), ACMEEmail: viper.GetString("acme_email"), ACMEURL: viper.GetString("acme_url"), diff --git a/hscontrol/types/node.go b/hscontrol/types/node.go index 3ccadc38..6bee5c42 100644 --- a/hscontrol/types/node.go +++ b/hscontrol/types/node.go @@ -394,23 +394,32 @@ func (node *Node) Proto() *v1.Node { return nodeProto } -func (node *Node) GetFQDN(dnsConfig *tailcfg.DNSConfig, baseDomain string) (string, error) { +func (node *Node) GetFQDN(cfg *Config, baseDomain string) (string, error) { var hostname string - if dnsConfig != nil && dnsConfig.Proxied { // MagicDNS + if cfg.DNSConfig != nil && cfg.DNSConfig.Proxied { // MagicDNS if node.GivenName == "" { return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeHasNoGivenName) } - if node.User.Name == "" { - return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeUserHasNoName) - } - hostname = fmt.Sprintf( - "%s.%s.%s", + "%s.%s", node.GivenName, - node.User.Name, baseDomain, ) + + if cfg.DNSUserNameInMagicDNS { + if node.User.Name == "" { + return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeUserHasNoName) + } + + hostname = fmt.Sprintf( + "%s.%s.%s", + node.GivenName, + node.User.Name, + baseDomain, + ) + } + if len(hostname) > MaxHostnameLength { return "", fmt.Errorf( "failed to create valid FQDN (%s): %w", diff --git a/hscontrol/types/node_test.go b/hscontrol/types/node_test.go index 157be89e..85857c3a 100644 --- a/hscontrol/types/node_test.go +++ b/hscontrol/types/node_test.go @@ -126,11 +126,87 @@ func TestNodeFQDN(t *testing.T) { tests := []struct { name string node Node - dns tailcfg.DNSConfig + cfg Config domain string want string wantErr string }{ + { + name: "all-set-with-username", + node: Node{ + GivenName: "test", + User: User{ + Name: "user", + }, + }, + cfg: Config{ + DNSConfig: &tailcfg.DNSConfig{ + Proxied: true, + }, + DNSUserNameInMagicDNS: true, + }, + domain: "example.com", + want: "test.user.example.com", + }, + { + name: "no-given-name-with-username", + node: Node{ + User: User{ + Name: "user", + }, + }, + cfg: Config{ + DNSConfig: &tailcfg.DNSConfig{ + Proxied: true, + }, + DNSUserNameInMagicDNS: true, + }, + domain: "example.com", + wantErr: "failed to create valid FQDN: node has no given name", + }, + { + name: "no-user-name-with-username", + node: Node{ + GivenName: "test", + User: User{}, + }, + cfg: Config{ + DNSConfig: &tailcfg.DNSConfig{ + Proxied: true, + }, + DNSUserNameInMagicDNS: true, + }, + domain: "example.com", + wantErr: "failed to create valid FQDN: node user has no name", + }, + { + name: "no-magic-dns-with-username", + node: Node{ + GivenName: "test", + User: User{ + Name: "user", + }, + }, + cfg: Config{ + DNSConfig: &tailcfg.DNSConfig{ + Proxied: false, + }, + DNSUserNameInMagicDNS: true, + }, + domain: "example.com", + want: "test", + }, + { + name: "no-dnsconfig-with-username", + node: Node{ + GivenName: "test", + User: User{ + Name: "user", + }, + }, + domain: "example.com", + want: "test", + }, { name: "all-set", node: Node{ @@ -139,11 +215,14 @@ func TestNodeFQDN(t *testing.T) { Name: "user", }, }, - dns: tailcfg.DNSConfig{ - Proxied: true, + cfg: Config{ + DNSConfig: &tailcfg.DNSConfig{ + Proxied: true, + }, + DNSUserNameInMagicDNS: false, }, domain: "example.com", - want: "test.user.example.com", + want: "test.example.com", }, { name: "no-given-name", @@ -152,8 +231,11 @@ func TestNodeFQDN(t *testing.T) { Name: "user", }, }, - dns: tailcfg.DNSConfig{ - Proxied: true, + cfg: Config{ + DNSConfig: &tailcfg.DNSConfig{ + Proxied: true, + }, + DNSUserNameInMagicDNS: false, }, domain: "example.com", wantErr: "failed to create valid FQDN: node has no given name", @@ -164,11 +246,14 @@ func TestNodeFQDN(t *testing.T) { GivenName: "test", User: User{}, }, - dns: tailcfg.DNSConfig{ - Proxied: true, + cfg: Config{ + DNSConfig: &tailcfg.DNSConfig{ + Proxied: true, + }, + DNSUserNameInMagicDNS: false, }, - domain: "example.com", - wantErr: "failed to create valid FQDN: node user has no name", + domain: "example.com", + want: "test.example.com", }, { name: "no-magic-dns", @@ -178,8 +263,11 @@ func TestNodeFQDN(t *testing.T) { Name: "user", }, }, - dns: tailcfg.DNSConfig{ - Proxied: false, + cfg: Config{ + DNSConfig: &tailcfg.DNSConfig{ + Proxied: false, + }, + DNSUserNameInMagicDNS: false, }, domain: "example.com", want: "test", @@ -199,7 +287,7 @@ func TestNodeFQDN(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - got, err := tc.node.GetFQDN(&tc.dns, tc.domain) + got, err := tc.node.GetFQDN(&tc.cfg, tc.domain) if (err != nil) && (err.Error() != tc.wantErr) { t.Errorf("GetFQDN() error = %s, wantErr %s", err, tc.wantErr) From 89ada557bc2e2772323e5380e89723815292f106 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 05:46:33 +0000 Subject: [PATCH 11/12] flake.lock: Update (#1991) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 51019abd..0ca54945 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1718276985, - "narHash": "sha256-u1fA0DYQYdeG+5kDm1bOoGcHtX0rtC7qs2YA2N1X++I=", + "lastModified": 1719468428, + "narHash": "sha256-vN5xJAZ4UGREEglh3lfbbkIj+MPEYMuqewMn4atZFaQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3f84a279f1a6290ce154c5531378acc827836fbb", + "rev": "1e3deb3d8a86a870d925760db1a5adecc64d329d", "type": "github" }, "original": { From eb1591df35624b6cbe85e5c671869a0806dedfba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 7 Jul 2024 06:16:36 +0000 Subject: [PATCH 12/12] flake.lock: Update (#2000) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 0ca54945..6de98223 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1719468428, - "narHash": "sha256-vN5xJAZ4UGREEglh3lfbbkIj+MPEYMuqewMn4atZFaQ=", + "lastModified": 1720181791, + "narHash": "sha256-i4vJL12/AdyuQuviMMd1Hk2tsGt02hDNhA0Zj1m16N8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1e3deb3d8a86a870d925760db1a5adecc64d329d", + "rev": "4284c2b73c8bce4b46a6adf23e16d9e2ec8da4bb", "type": "github" }, "original": {