From 987bbee1dbe9de611bc409e0f39647e460fda25e Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 24 Aug 2021 07:09:47 +0100 Subject: [PATCH 1/7] Add DNSConfig field to configuration --- app.go | 2 ++ cmd/headscale/cli/utils.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/app.go b/app.go index e5f44103..c903d83f 100644 --- a/app.go +++ b/app.go @@ -43,6 +43,8 @@ type Config struct { TLSCertPath string TLSKeyPath string + + DNSConfig *tailcfg.DNSConfig } // Headscale represents the base app of the service diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 7e7e8f96..7ba78648 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -41,6 +41,8 @@ func LoadConfig(path string) error { viper.SetDefault("log_level", "info") + viper.SetDefault("dns_config", nil) + err := viper.ReadInConfig() if err != nil { return fmt.Errorf("Fatal error reading config file: %s \n", err) @@ -70,6 +72,40 @@ func LoadConfig(path string) error { } else { return nil } + +} + +func getDNSConfig() *tailcfg.DNSConfig { + if viper.IsSet("dns_config") { + dnsConfig := &tailcfg.DNSConfig{} + + if viper.IsSet("dns_config.nameservers") { + nameserversStr := viper.GetStringSlice("dns_config.nameservers") + + nameservers := make([]netaddr.IP, len(nameserversStr)) + + for index, nameserverStr := range nameserversStr { + nameserver, err := netaddr.ParseIP(nameserverStr) + if err != nil { + log.Error(). + Str("func", "getDNSConfig"). + Err(err). + Msgf("Could not parse nameserver IP: %s", nameserverStr) + } + + nameservers[index] = nameserver + } + + dnsConfig.Nameservers = nameservers + } + if viper.IsSet("dns_config.domains") { + dnsConfig.Domains = viper.GetStringSlice("dns_config.domains") + } + + return dnsConfig + } + + return nil } func absPath(path string) string { @@ -126,6 +162,8 @@ func getHeadscaleApp() (*headscale.Headscale, error) { TLSCertPath: absPath(viper.GetString("tls_cert_path")), TLSKeyPath: absPath(viper.GetString("tls_key_path")), + + DNSConfig: getDNSConfig(), } h, err := headscale.NewHeadscale(cfg) From e77c16b55a949d4d181a839771269e6335d03916 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 24 Aug 2021 07:10:09 +0100 Subject: [PATCH 2/7] Add DNSConfig to example and setup test --- cmd/headscale/headscale_test.go | 3 ++- config.json.postgres.example | 7 ++++++- config.json.sqlite.example | 7 ++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd/headscale/headscale_test.go b/cmd/headscale/headscale_test.go index 8fcf8a54..58a0977e 100644 --- a/cmd/headscale/headscale_test.go +++ b/cmd/headscale/headscale_test.go @@ -58,7 +58,7 @@ func (*Suite) TestPostgresConfigLoading(c *check.C) { c.Assert(viper.GetString("db_port"), check.Equals, "5432") c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "") c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http") - c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01") + c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1") } func (*Suite) TestSqliteConfigLoading(c *check.C) { @@ -92,6 +92,7 @@ func (*Suite) TestSqliteConfigLoading(c *check.C) { c.Assert(viper.GetString("tls_letsencrypt_hostname"), check.Equals, "") c.Assert(viper.GetString("tls_letsencrypt_listen"), check.Equals, ":http") c.Assert(viper.GetString("tls_letsencrypt_challenge_type"), check.Equals, "HTTP-01") + c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1") } func writeConfig(c *check.C, tmpDir string, configYaml []byte) { diff --git a/config.json.postgres.example b/config.json.postgres.example index fe772d73..aba72063 100644 --- a/config.json.postgres.example +++ b/config.json.postgres.example @@ -16,5 +16,10 @@ "tls_letsencrypt_challenge_type": "HTTP-01", "tls_cert_path": "", "tls_key_path": "", - "acl_policy_path": "" + "acl_policy_path": "", + "dns_config": { + "nameservers": [ + "1.1.1.1" + ] + } } diff --git a/config.json.sqlite.example b/config.json.sqlite.example index e965059a..b22e5ace 100644 --- a/config.json.sqlite.example +++ b/config.json.sqlite.example @@ -12,5 +12,10 @@ "tls_letsencrypt_challenge_type": "HTTP-01", "tls_cert_path": "", "tls_key_path": "", - "acl_policy_path": "" + "acl_policy_path": "", + "dns_config": { + "nameservers": [ + "1.1.1.1" + ] + } } From 01e781e546299627ecf47f5750bb211b15b99b0d Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 24 Aug 2021 07:11:45 +0100 Subject: [PATCH 3/7] Pass DNSConfig to nodes in MapResponse --- api.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/api.go b/api.go index 7a6b4b16..621eeb8f 100644 --- a/api.go +++ b/api.go @@ -14,7 +14,6 @@ import ( "github.com/gin-gonic/gin" "github.com/klauspost/compress/zstd" "gorm.io/gorm" - "inet.af/netaddr" "tailscale.com/tailcfg" "tailscale.com/types/wgkey" ) @@ -245,10 +244,15 @@ func (h *Headscale) getMapResponse(mKey wgkey.Key, req tailcfg.MapRequest, m Mac } resp := tailcfg.MapResponse{ - KeepAlive: false, - Node: node, - Peers: *peers, - DNS: []netaddr.IP{}, + KeepAlive: false, + Node: node, + Peers: *peers, + //TODO(kradalby): As per tailscale docs, if DNSConfig is nil, + // it means its not updated, maybe we can have some logic + // to check and only pass updates when its updates. + // This is probably more relevant if we try to implement + // "MagicDNS" + DNSConfig: h.cfg.DNSConfig, SearchPaths: []string{}, Domain: "headscale.net", PacketFilter: *h.aclRules, From b3732e7fb9d9dd28da35ac9c9b62a99e483da860 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 25 Aug 2021 07:04:48 +0100 Subject: [PATCH 4/7] Add nameserver as resolver aswell --- cmd/headscale/cli/utils.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 7ba78648..b5c7c219 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -83,6 +83,7 @@ func getDNSConfig() *tailcfg.DNSConfig { nameserversStr := viper.GetStringSlice("dns_config.nameservers") nameservers := make([]netaddr.IP, len(nameserversStr)) + resolvers := make([]tailcfg.DNSResolver, len(nameserversStr)) for index, nameserverStr := range nameserversStr { nameserver, err := netaddr.ParseIP(nameserverStr) @@ -94,9 +95,13 @@ func getDNSConfig() *tailcfg.DNSConfig { } nameservers[index] = nameserver + resolvers[index] = tailcfg.DNSResolver{ + Addr: nameserver.String() + ":53", + } } dnsConfig.Nameservers = nameservers + dnsConfig.Resolvers = resolvers } if viper.IsSet("dns_config.domains") { dnsConfig.Domains = viper.GetStringSlice("dns_config.domains") From 3f5e06a0f8c4344dcd41a7d433aa7ff1627c2d91 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 25 Aug 2021 18:43:13 +0100 Subject: [PATCH 5/7] Dont add the portnumber to the ip --- cmd/headscale/cli/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index b5c7c219..e3c4402a 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -96,7 +96,7 @@ func getDNSConfig() *tailcfg.DNSConfig { nameservers[index] = nameserver resolvers[index] = tailcfg.DNSResolver{ - Addr: nameserver.String() + ":53", + Addr: nameserver.String(), } } From 8735e5675cf61ea3ea4e797f5155943966b056eb Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 25 Aug 2021 19:03:04 +0100 Subject: [PATCH 6/7] Add a test for the getdnsconfig function --- cmd/headscale/cli/utils.go | 4 ++-- cmd/headscale/headscale_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index e3c4402a..aaf994d0 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -75,7 +75,7 @@ func LoadConfig(path string) error { } -func getDNSConfig() *tailcfg.DNSConfig { +func GetDNSConfig() *tailcfg.DNSConfig { if viper.IsSet("dns_config") { dnsConfig := &tailcfg.DNSConfig{} @@ -168,7 +168,7 @@ func getHeadscaleApp() (*headscale.Headscale, error) { TLSCertPath: absPath(viper.GetString("tls_cert_path")), TLSKeyPath: absPath(viper.GetString("tls_key_path")), - DNSConfig: getDNSConfig(), + DNSConfig: GetDNSConfig(), } h, err := headscale.NewHeadscale(cfg) diff --git a/cmd/headscale/headscale_test.go b/cmd/headscale/headscale_test.go index 58a0977e..58bf5899 100644 --- a/cmd/headscale/headscale_test.go +++ b/cmd/headscale/headscale_test.go @@ -95,6 +95,36 @@ func (*Suite) TestSqliteConfigLoading(c *check.C) { c.Assert(viper.GetStringSlice("dns_config.nameservers")[0], check.Equals, "1.1.1.1") } +func (*Suite) TestDNSConfigLoading(c *check.C) { + tmpDir, err := ioutil.TempDir("", "headscale") + if err != nil { + c.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + path, err := os.Getwd() + if err != nil { + c.Fatal(err) + } + + // Symlink the example config file + err = os.Symlink(filepath.Clean(path+"/../../config.json.sqlite.example"), filepath.Join(tmpDir, "config.json")) + if err != nil { + c.Fatal(err) + } + + // Load example config, it should load without validation errors + err = cli.LoadConfig(tmpDir) + c.Assert(err, check.IsNil) + + dnsConfig := cli.GetDNSConfig() + fmt.Println(dnsConfig) + + c.Assert(dnsConfig.Nameservers[0].String(), check.Equals, "1.1.1.1") + + c.Assert(dnsConfig.Resolvers[0].Addr, check.Equals, "1.1.1.1") +} + func writeConfig(c *check.C, tmpDir string, configYaml []byte) { // Populate a custom config file configFile := filepath.Join(tmpDir, "config.yaml") From ba3dffecbfeb616660f66500ae08299d6d43be14 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 25 Aug 2021 19:05:10 +0100 Subject: [PATCH 7/7] Update readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb42b666..fef70207 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,9 @@ Headscale implements this coordination server. - [X] JSON-formatted output - [X] ACLs - [X] Support for alternative IP ranges in the tailnets (default Tailscale's 100.64.0.0/10) +- [X] DNS (passing DNS servers to nodes) - [ ] Share nodes between ~~users~~ namespaces -- [ ] DNS +- [ ] MagicDNS / Smart DNS ## Roadmap 🤷