diff --git a/.goreleaser.yml b/.goreleaser.yml index f7355104..ad4fea70 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -60,6 +60,9 @@ builds: - linux goarch: - arm64 + env: + - CGO_ENABLED=1 + - CC=aarch64-linux-gnu-gcc main: ./cmd/headscale/headscale.go mod_timestamp: '{{ .CommitTimestamp }}' ldflags: diff --git a/app.go b/app.go index 864ae16a..66e2a306 100644 --- a/app.go +++ b/app.go @@ -113,7 +113,10 @@ func NewHeadscale(cfg Config) (*Headscale, error) { if err != nil { return nil, err } - h.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver) + // we might have routes already from Split DNS + if h.cfg.DNSConfig.Routes == nil { + h.cfg.DNSConfig.Routes = make(map[string][]dnstype.Resolver) + } for _, d := range magicDNSDomains { h.cfg.DNSConfig.Routes[d.WithoutTrailingDot()] = nil } diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 95555e94..52c8d043 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -104,6 +104,33 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) { dnsConfig.Nameservers = nameservers dnsConfig.Resolvers = resolvers } + + if viper.IsSet("dns_config.restricted_nameservers") { + if len(dnsConfig.Nameservers) > 0 { + dnsConfig.Routes = make(map[string][]dnstype.Resolver) + restrictedDNS := viper.GetStringMapStringSlice("dns_config.restricted_nameservers") + for domain, restrictedNameservers := range restrictedDNS { + restrictedResolvers := make([]dnstype.Resolver, len(restrictedNameservers)) + for index, nameserverStr := range restrictedNameservers { + nameserver, err := netaddr.ParseIP(nameserverStr) + if err != nil { + log.Error(). + Str("func", "getDNSConfig"). + Err(err). + Msgf("Could not parse restricted nameserver IP: %s", nameserverStr) + } + restrictedResolvers[index] = dnstype.Resolver{ + Addr: nameserver.String(), + } + } + dnsConfig.Routes[domain] = restrictedResolvers + } + } else { + log.Warn(). + Msg("Warning: dns_config.restricted_nameservers is set, but no nameservers are configured. Ignoring restricted_nameservers.") + } + } + if viper.IsSet("dns_config.domains") { dnsConfig.Domains = viper.GetStringSlice("dns_config.domains") } diff --git a/docs/DNS.md b/docs/DNS.md index 85bf9f44..10f99b79 100644 --- a/docs/DNS.md +++ b/docs/DNS.md @@ -11,23 +11,29 @@ Long story short, you can define the DNS servers you want to use in your tailnet ## Configuration reference -The setup is done via the `config.json` file, under the `dns_config` key. +The setup is done via the `config.yaml` file, under the `dns_config` key. -```json -{ - "server_url": "http://127.0.0.1:8001", - "listen_addr": "0.0.0.0:8001", - "private_key_path": "private.key", - //... - "dns_config": { - "nameservers": ["1.1.1.1", "8.8.8.8"], - "domains": [], - "magic_dns": true, - "base_domain": "example.com" - } -} +```yaml +server_url: http://127.0.0.1:8001 +listen_addr: 0.0.0.0:8001 +private_key_path: private.key +dns_config: + nameservers: + - 1.1.1.1 + - 8.8.8.8 + restricted_nameservers: + foo.bar.com: + - 1.1.1.1 + darp.headscale.net: + - 1.1.1.1 + - 8.8.8.8 + domains: [] + magic_dns: true + base_domain: example.com ``` + - `nameservers`: The list of DNS servers to use. - `domains`: Search domains to inject. - `magic_dns`: Whether to use [MagicDNS](https://tailscale.com/kb/1081/magicdns/). Only works if there is at least a nameserver defined. -- `base_domain`: 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 `hostname.namespace.base_domain` (e.g., _myhost.mynamespace.example.com_). \ No newline at end of file +- `base_domain`: 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 `hostname.namespace.base_domain` (e.g., _myhost.mynamespace.example.com_). +- `restricted_nameservers`: Split DNS (see https://tailscale.com/kb/1054/dns/), list of search domains and the DNS to query for each one. \ No newline at end of file