Skip to content

Commit

Permalink
add dns.extra_records_path option
Browse files Browse the repository at this point in the history
Signed-off-by: Kristoffer Dalby <[email protected]>
  • Loading branch information
kradalby committed Dec 9, 2024
1 parent 46e7f77 commit 8138490
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ This will also affect the way you [reference users in policies](https://github.c
- Fixed updating of hostname and givenName when it is updated in HostInfo [#2199](https://github.com/juanfont/headscale/pull/2199)
- Fixed missing `stable-debug` container tag [#2232](https://github.com/juanfont/headscale/pr/2232)
- Loosened up `server_url` and `base_domain` check. It was overly strict in some cases.
- Add `dns.extra_records_path` configuration option [#2262](https://github.com/juanfont/headscale/issues/2262)

## 0.23.0 (2024-09-18)

Expand Down
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

# When updating go.mod or go.sum, a new sha will need to be calculated,
# update this if you have a mismatch after doing a change to thos files.
vendorHash = "sha256-Lgm6ysif83mqd7EmdBzV3QVXkVqXl7fh9THHUdopzhY=";
vendorHash = "sha256-PZwpnfG+yCjRQvPc3XSy3Sp9HkpNGDEzj+xsTsduyFs=";

subPackages = ["cmd/headscale"];

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
github.com/gaissmai/bart v0.11.1 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
Expand Down
34 changes: 31 additions & 3 deletions hscontrol/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/juanfont/headscale/hscontrol/db"
"github.com/juanfont/headscale/hscontrol/derp"
derpServer "github.com/juanfont/headscale/hscontrol/derp/server"
"github.com/juanfont/headscale/hscontrol/dns"
"github.com/juanfont/headscale/hscontrol/mapper"
"github.com/juanfont/headscale/hscontrol/notifier"
"github.com/juanfont/headscale/hscontrol/policy"
Expand Down Expand Up @@ -88,8 +89,9 @@ type Headscale struct {
DERPMap *tailcfg.DERPMap
DERPServer *derpServer.DERPServer

polManOnce sync.Once
polMan policy.PolicyManager
polManOnce sync.Once
polMan policy.PolicyManager
extraRecordMan *dns.ExtraRecordsMan

mapper *mapper.Mapper
nodeNotifier *notifier.Notifier
Expand Down Expand Up @@ -245,12 +247,18 @@ func (h *Headscale) scheduledTasks(ctx context.Context) {

derpTicker := time.NewTicker(h.cfg.DERP.UpdateFrequency)
defer derpTicker.Stop()

// If we dont want auto update, just stop the ticker
if !h.cfg.DERP.AutoUpdate {
derpTicker.Stop()
}

var extraRecordsUpdate <-chan []tailcfg.DNSRecord
if h.extraRecordMan != nil {
extraRecordsUpdate = h.extraRecordMan.Updates()
} else {
extraRecordsUpdate = make(chan []tailcfg.DNSRecord)
}

for {
select {
case <-ctx.Done():
Expand Down Expand Up @@ -289,6 +297,19 @@ func (h *Headscale) scheduledTasks(ctx context.Context) {
Type: types.StateDERPUpdated,
DERPMap: h.DERPMap,
})

case records, ok := <-extraRecordsUpdate:
if !ok {
continue
}
h.cfg.TailcfgDNSConfig.ExtraRecords = records

ctx := types.NotifyCtx(context.Background(), "acl-users-change", "all")
h.nodeNotifier.NotifyAll(ctx, types.StateUpdate{
// TODO(kradalby): We can probably do better than sending a full update here,
// but for now this will ensure that all of the nodes get the new records.
Type: types.StateFullUpdate,
})
}
}
}
Expand Down Expand Up @@ -577,6 +598,13 @@ func (h *Headscale) Serve() error {
h.ephemeralGC.Schedule(node.ID, h.cfg.EphemeralNodeInactivityTimeout)
}

if h.cfg.DNSConfig.ExtraRecordsPath != "" {
h.extraRecordMan, err = dns.NewExtraRecordsMan(h.cfg.DNSConfig.ExtraRecordsPath)
if err != nil {
return fmt.Errorf("setting up : %w", err)
}
}

// Start all scheduled tasks, e.g. expiring nodes, derp updates and
// records updates
scheduleCtx, scheduleCancel := context.WithCancel(context.Background())
Expand Down
125 changes: 125 additions & 0 deletions hscontrol/dns/extrarecords.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package dns

import (
"crypto/sha256"
"encoding/json"
"fmt"
"os"
"sync"

"github.com/fsnotify/fsnotify"
"github.com/rs/zerolog/log"
"tailscale.com/tailcfg"
"tailscale.com/util/set"
)

type ExtraRecordsMan struct {
mu sync.RWMutex
records set.Set[tailcfg.DNSRecord]
watcher *fsnotify.Watcher
path string

updateChan chan []tailcfg.DNSRecord
closeCh chan struct{}
hashes map[string][32]byte
}

func NewExtraRecordsMan(path string) (*ExtraRecordsMan, error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, fmt.Errorf("creating watcher: %w", err)
}
defer watcher.Close()

err = watcher.Add(path)
if err != nil {
return nil, fmt.Errorf("adding path to watcher: %w", err)
}

return &ExtraRecordsMan{
watcher: watcher,
path: path,
records: set.Set[tailcfg.DNSRecord]{},
hashes: map[string][32]byte{},
closeCh: make(chan struct{}),
}, nil
}

func (e *ExtraRecordsMan) Records() []tailcfg.DNSRecord {
e.mu.RLock()
defer e.mu.RUnlock()

return e.records.Slice()
}

func (e *ExtraRecordsMan) Updates() <-chan []tailcfg.DNSRecord {
return e.updateChan
}

func (e *ExtraRecordsMan) Run() {
for {
select {
case <-e.closeCh:
return
case _, ok := <-e.watcher.Events:
if !ok {
log.Error().Msgf("error reading file watcher event of channel, records watcher closing")
return
}
e.updateRecords()

case err, ok := <-e.watcher.Errors:
if !ok {
log.Error().Msgf("error reading file watcher event of channel, records watcher closing")
return
}
log.Error().Err(err).Msgf("extra records filewatcher returned error")
}
}
}

func (e *ExtraRecordsMan) Close() {
close(e.closeCh)
}

func (e *ExtraRecordsMan) updateRecords() {
records, newHash, err := readExtraRecordsFromPath(e.path)
if err != nil {
log.Error().Err(err).Msgf("reading extra records from path: %s", e.path)
return
}

e.mu.Lock()
defer e.mu.Unlock()

// If there has not been any change, ignore the update.
if oldHash, ok := e.hashes[e.path]; ok {
if newHash == oldHash {
return
}
}

e.records = set.SetOf(records)
e.hashes[e.path] = newHash

e.updateChan <- e.records.Slice()
}

// readExtraRecordsFromPath reads a JSON file of tailcfg.DNSRecord
// and returns the records and the hash of the file.
func readExtraRecordsFromPath(path string) ([]tailcfg.DNSRecord, [32]byte, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, [32]byte{}, fmt.Errorf("reading path: %s, err: %w", path, err)
}

var records []tailcfg.DNSRecord
err = json.Unmarshal(b, &records)
if err != nil {
return nil, [32]byte{}, fmt.Errorf("unmarshalling records: %w", err)
}

hash := sha256.Sum256(b)

return records, hash, nil
}
17 changes: 12 additions & 5 deletions hscontrol/types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,12 @@ type Config struct {
}

type DNSConfig struct {
MagicDNS bool `mapstructure:"magic_dns"`
BaseDomain string `mapstructure:"base_domain"`
Nameservers Nameservers
SearchDomains []string `mapstructure:"search_domains"`
ExtraRecords []tailcfg.DNSRecord `mapstructure:"extra_records"`
MagicDNS bool `mapstructure:"magic_dns"`
BaseDomain string `mapstructure:"base_domain"`
Nameservers Nameservers
SearchDomains []string `mapstructure:"search_domains"`
ExtraRecords []tailcfg.DNSRecord `mapstructure:"extra_records"`
ExtraRecordsPath string `mapstructure:"extra_records_path"`
}

type Nameservers struct {
Expand Down Expand Up @@ -261,6 +262,7 @@ func LoadConfig(path string, isFile bool) error {
viper.SetDefault("dns.nameservers.split", map[string]string{})
viper.SetDefault("dns.search_domains", []string{})
viper.SetDefault("dns.extra_records", []tailcfg.DNSRecord{})
viper.SetDefault("dns.extra_records_path", "")

viper.SetDefault("derp.server.enabled", false)
viper.SetDefault("derp.server.stun.enabled", true)
Expand Down Expand Up @@ -352,6 +354,10 @@ func validateServerConfig() error {
}
}

if viper.IsSet("dns.extra_records") && viper.IsSet("dns.extra_records_path") {
log.Fatal().Msg("Fatal config error: dns.extra_records and dns.extra_records_path are mutually exclusive. Please remove one of them from your config file")
}

// Collect any validation errors and return them all at once
var errorText string
if (viper.GetString("tls_letsencrypt_hostname") != "") &&
Expand Down Expand Up @@ -594,6 +600,7 @@ func dns() (DNSConfig, error) {
dns.Nameservers.Global = viper.GetStringSlice("dns.nameservers.global")
dns.Nameservers.Split = viper.GetStringMapStringSlice("dns.nameservers.split")
dns.SearchDomains = viper.GetStringSlice("dns.search_domains")
dns.ExtraRecordsPath = viper.GetString("dns.extra_records_path")

if viper.IsSet("dns.extra_records") {
var extraRecords []tailcfg.DNSRecord
Expand Down

0 comments on commit 8138490

Please sign in to comment.