fix search domains and remove username from magicdns (#1987)
This commit is contained in:
parent
4a34cfc4a6
commit
14a3f94f0c
8 changed files with 183 additions and 63 deletions
|
@ -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)
|
- 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`
|
- `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)
|
- `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
|
### Changes
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,15 @@ dns_config:
|
||||||
# Only works if there is at least a nameserver defined.
|
# Only works if there is at least a nameserver defined.
|
||||||
magic_dns: true
|
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.
|
# Defines the base domain to create the hostnames for MagicDNS.
|
||||||
# `base_domain` must be a FQDNs, without the trailing dot.
|
# `base_domain` must be a FQDNs, without the trailing dot.
|
||||||
# The FQDN of the hosts will be
|
# The FQDN of the hosts will be
|
||||||
|
|
|
@ -122,37 +122,41 @@ func generateUserProfiles(
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateDNSConfig(
|
func generateDNSConfig(
|
||||||
base *tailcfg.DNSConfig,
|
cfg *types.Config,
|
||||||
baseDomain string,
|
baseDomain string,
|
||||||
node *types.Node,
|
node *types.Node,
|
||||||
peers types.Nodes,
|
peers types.Nodes,
|
||||||
) *tailcfg.DNSConfig {
|
) *tailcfg.DNSConfig {
|
||||||
dnsConfig := base.Clone()
|
if cfg.DNSConfig == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsConfig := cfg.DNSConfig.Clone()
|
||||||
|
|
||||||
// if MagicDNS is enabled
|
// if MagicDNS is enabled
|
||||||
if base != nil && base.Proxied {
|
if dnsConfig.Proxied {
|
||||||
// Only inject the Search Domain of the current user
|
if cfg.DNSUserNameInMagicDNS {
|
||||||
// shared nodes should use their full FQDN
|
// Only inject the Search Domain of the current user
|
||||||
dnsConfig.Domains = append(
|
// shared nodes should use their full FQDN
|
||||||
dnsConfig.Domains,
|
dnsConfig.Domains = append(
|
||||||
fmt.Sprintf(
|
dnsConfig.Domains,
|
||||||
"%s.%s",
|
fmt.Sprintf(
|
||||||
node.User.Name,
|
"%s.%s",
|
||||||
baseDomain,
|
node.User.Name,
|
||||||
),
|
baseDomain,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
|
||||||
userSet := mapset.NewSet[types.User]()
|
userSet := mapset.NewSet[types.User]()
|
||||||
userSet.Add(node.User)
|
userSet.Add(node.User)
|
||||||
for _, p := range peers {
|
for _, p := range peers {
|
||||||
userSet.Add(p.User)
|
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)
|
addNextDNSMetadata(dnsConfig.Resolvers, node)
|
||||||
|
@ -568,7 +572,7 @@ func appendPeerChanges(
|
||||||
profiles := generateUserProfiles(node, changed, cfg.BaseDomain)
|
profiles := generateUserProfiles(node, changed, cfg.BaseDomain)
|
||||||
|
|
||||||
dnsConfig := generateDNSConfig(
|
dnsConfig := generateDNSConfig(
|
||||||
cfg.DNSConfig,
|
cfg,
|
||||||
cfg.BaseDomain,
|
cfg.BaseDomain,
|
||||||
node,
|
node,
|
||||||
peers,
|
peers,
|
||||||
|
|
|
@ -127,7 +127,10 @@ func TestDNSConfigMapResponse(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
got := generateDNSConfig(
|
got := generateDNSConfig(
|
||||||
&dnsConfigOrig,
|
&types.Config{
|
||||||
|
DNSConfig: &dnsConfigOrig,
|
||||||
|
DNSUserNameInMagicDNS: true,
|
||||||
|
},
|
||||||
baseDomain,
|
baseDomain,
|
||||||
nodeInShared1,
|
nodeInShared1,
|
||||||
peersOfNodeInShared1,
|
peersOfNodeInShared1,
|
||||||
|
@ -187,9 +190,9 @@ func Test_fullMapResponse(t *testing.T) {
|
||||||
UserID: 0,
|
UserID: 0,
|
||||||
User: types.User{Name: "mini"},
|
User: types.User{Name: "mini"},
|
||||||
ForcedTags: []string{},
|
ForcedTags: []string{},
|
||||||
AuthKey: &types.PreAuthKey{},
|
AuthKey: &types.PreAuthKey{},
|
||||||
LastSeen: &lastSeen,
|
LastSeen: &lastSeen,
|
||||||
Expiry: &expire,
|
Expiry: &expire,
|
||||||
Hostinfo: &tailcfg.Hostinfo{},
|
Hostinfo: &tailcfg.Hostinfo{},
|
||||||
Routes: []types.Route{
|
Routes: []types.Route{
|
||||||
{
|
{
|
||||||
|
|
|
@ -77,7 +77,7 @@ func tailNode(
|
||||||
keyExpiry = time.Time{}
|
keyExpiry = time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
hostname, err := node.GetFQDN(cfg.DNSConfig, cfg.BaseDomain)
|
hostname, err := node.GetFQDN(cfg, cfg.BaseDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("tailNode, failed to create FQDN: %s", err)
|
return nil, fmt.Errorf("tailNode, failed to create FQDN: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,8 @@ type Config struct {
|
||||||
ACMEURL string
|
ACMEURL string
|
||||||
ACMEEmail string
|
ACMEEmail string
|
||||||
|
|
||||||
DNSConfig *tailcfg.DNSConfig
|
DNSConfig *tailcfg.DNSConfig
|
||||||
|
DNSUserNameInMagicDNS bool
|
||||||
|
|
||||||
UnixSocket string
|
UnixSocket string
|
||||||
UnixSocketPermission fs.FileMode
|
UnixSocketPermission fs.FileMode
|
||||||
|
@ -204,6 +205,7 @@ func LoadConfig(path string, isFile bool) error {
|
||||||
|
|
||||||
viper.SetDefault("dns_config", nil)
|
viper.SetDefault("dns_config", nil)
|
||||||
viper.SetDefault("dns_config.override_local_dns", true)
|
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.enabled", false)
|
||||||
viper.SetDefault("derp.server.stun.enabled", true)
|
viper.SetDefault("derp.server.stun.enabled", true)
|
||||||
|
@ -540,16 +542,6 @@ func GetDNSConfig() (*tailcfg.DNSConfig, string) {
|
||||||
dnsConfig.Domains = domains
|
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") {
|
if viper.IsSet("dns_config.extra_records") {
|
||||||
var extraRecords []tailcfg.DNSRecord
|
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
|
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
|
return dnsConfig, baseDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -719,7 +721,8 @@ func GetHeadscaleConfig() (*Config, error) {
|
||||||
|
|
||||||
TLS: GetTLSConfig(),
|
TLS: GetTLSConfig(),
|
||||||
|
|
||||||
DNSConfig: dnsConfig,
|
DNSConfig: dnsConfig,
|
||||||
|
DNSUserNameInMagicDNS: viper.GetBool("dns_config.use_username_in_magic_dns"),
|
||||||
|
|
||||||
ACMEEmail: viper.GetString("acme_email"),
|
ACMEEmail: viper.GetString("acme_email"),
|
||||||
ACMEURL: viper.GetString("acme_url"),
|
ACMEURL: viper.GetString("acme_url"),
|
||||||
|
|
|
@ -394,23 +394,32 @@ func (node *Node) Proto() *v1.Node {
|
||||||
return nodeProto
|
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
|
var hostname string
|
||||||
if dnsConfig != nil && dnsConfig.Proxied { // MagicDNS
|
if cfg.DNSConfig != nil && cfg.DNSConfig.Proxied { // MagicDNS
|
||||||
if node.GivenName == "" {
|
if node.GivenName == "" {
|
||||||
return "", fmt.Errorf("failed to create valid FQDN: %w", ErrNodeHasNoGivenName)
|
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(
|
hostname = fmt.Sprintf(
|
||||||
"%s.%s.%s",
|
"%s.%s",
|
||||||
node.GivenName,
|
node.GivenName,
|
||||||
node.User.Name,
|
|
||||||
baseDomain,
|
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 {
|
if len(hostname) > MaxHostnameLength {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"failed to create valid FQDN (%s): %w",
|
"failed to create valid FQDN (%s): %w",
|
||||||
|
|
|
@ -126,11 +126,87 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
node Node
|
node Node
|
||||||
dns tailcfg.DNSConfig
|
cfg Config
|
||||||
domain string
|
domain string
|
||||||
want string
|
want string
|
||||||
wantErr 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",
|
name: "all-set",
|
||||||
node: Node{
|
node: Node{
|
||||||
|
@ -139,11 +215,14 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
Name: "user",
|
Name: "user",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dns: tailcfg.DNSConfig{
|
cfg: Config{
|
||||||
Proxied: true,
|
DNSConfig: &tailcfg.DNSConfig{
|
||||||
|
Proxied: true,
|
||||||
|
},
|
||||||
|
DNSUserNameInMagicDNS: false,
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
want: "test.user.example.com",
|
want: "test.example.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no-given-name",
|
name: "no-given-name",
|
||||||
|
@ -152,8 +231,11 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
Name: "user",
|
Name: "user",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dns: tailcfg.DNSConfig{
|
cfg: Config{
|
||||||
Proxied: true,
|
DNSConfig: &tailcfg.DNSConfig{
|
||||||
|
Proxied: true,
|
||||||
|
},
|
||||||
|
DNSUserNameInMagicDNS: false,
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
wantErr: "failed to create valid FQDN: node has no given name",
|
wantErr: "failed to create valid FQDN: node has no given name",
|
||||||
|
@ -164,11 +246,14 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
GivenName: "test",
|
GivenName: "test",
|
||||||
User: User{},
|
User: User{},
|
||||||
},
|
},
|
||||||
dns: tailcfg.DNSConfig{
|
cfg: Config{
|
||||||
Proxied: true,
|
DNSConfig: &tailcfg.DNSConfig{
|
||||||
|
Proxied: true,
|
||||||
|
},
|
||||||
|
DNSUserNameInMagicDNS: false,
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
wantErr: "failed to create valid FQDN: node user has no name",
|
want: "test.example.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no-magic-dns",
|
name: "no-magic-dns",
|
||||||
|
@ -178,8 +263,11 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
Name: "user",
|
Name: "user",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dns: tailcfg.DNSConfig{
|
cfg: Config{
|
||||||
Proxied: false,
|
DNSConfig: &tailcfg.DNSConfig{
|
||||||
|
Proxied: false,
|
||||||
|
},
|
||||||
|
DNSUserNameInMagicDNS: false,
|
||||||
},
|
},
|
||||||
domain: "example.com",
|
domain: "example.com",
|
||||||
want: "test",
|
want: "test",
|
||||||
|
@ -199,7 +287,7 @@ func TestNodeFQDN(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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) {
|
if (err != nil) && (err.Error() != tc.wantErr) {
|
||||||
t.Errorf("GetFQDN() error = %s, wantErr %s", err, tc.wantErr)
|
t.Errorf("GetFQDN() error = %s, wantErr %s", err, tc.wantErr)
|
||||||
|
|
Loading…
Reference in a new issue