Skip to content

Commit

Permalink
fix search domains and remove username from magicdns (#1987)
Browse files Browse the repository at this point in the history
  • Loading branch information
kradalby authored Jun 26, 2024
1 parent 4a34cfc commit 14a3f94
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 64 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 9 additions & 0 deletions config-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 29 additions & 25 deletions hscontrol/mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

// 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,
),
)
dnsConfig := cfg.DNSConfig.Clone()

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
// if MagicDNS is enabled
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)
}
for _, user := range userSet.ToSlice() {
dnsRoute := fmt.Sprintf("%v.%v", user.Name, baseDomain)
dnsConfig.Routes[dnsRoute] = nil
}
}
} else {
dnsConfig = base
}

addNextDNSMetadata(dnsConfig.Resolvers, node)
Expand Down Expand Up @@ -568,7 +572,7 @@ func appendPeerChanges(
profiles := generateUserProfiles(node, changed, cfg.BaseDomain)

dnsConfig := generateDNSConfig(
cfg.DNSConfig,
cfg,
cfg.BaseDomain,
node,
peers,
Expand Down
11 changes: 7 additions & 4 deletions hscontrol/mapper/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ func TestDNSConfigMapResponse(t *testing.T) {
}

got := generateDNSConfig(
&dnsConfigOrig,
&types.Config{
DNSConfig: &dnsConfigOrig,
DNSUserNameInMagicDNS: true,
},
baseDomain,
nodeInShared1,
peersOfNodeInShared1,
Expand Down Expand Up @@ -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{
{
Expand Down
2 changes: 1 addition & 1 deletion hscontrol/mapper/tail.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
29 changes: 16 additions & 13 deletions hscontrol/types/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ type Config struct {
ACMEURL string
ACMEEmail string

DNSConfig *tailcfg.DNSConfig
DNSConfig *tailcfg.DNSConfig
DNSUserNameInMagicDNS bool

UnixSocket string
UnixSocketPermission fs.FileMode
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand All @@ -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
}

Expand Down Expand Up @@ -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"),
Expand Down
25 changes: 17 additions & 8 deletions hscontrol/types/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading

0 comments on commit 14a3f94

Please sign in to comment.