net/dns,docs/windows/policy,util/syspolicy: register Tailscale IP addresses in AD DNS if required by policy

In this PR, we make DNS registration behavior configurable via the EnableDNSRegistration policy setting.
We keep the default behavior unchanged, but allow admins to either enforce DNS registration and dynamic
DNS updates for the Tailscale interface, or prevent Tailscale from modifying the settings configured in
the network adapter's properties or by other means.

Updates #14917

Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
Nick Khyl
2025-05-08 18:38:48 -05:00
committed by Nick Khyl
parent 2c16fcaa06
commit fb188c5b53
5 changed files with 121 additions and 12 deletions

View File

@@ -29,6 +29,9 @@ import (
"tailscale.com/health"
"tailscale.com/types/logger"
"tailscale.com/util/dnsname"
"tailscale.com/util/syspolicy"
"tailscale.com/util/syspolicy/rsop"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/util/winutil"
)
@@ -45,6 +48,8 @@ type windowsManager struct {
nrptDB *nrptRuleDatabase
wslManager *wslManager
unregisterPolicyChangeCb func() // called when the manager is closing
mu sync.Mutex
closing bool
}
@@ -64,6 +69,11 @@ func NewOSConfigurator(logf logger.Logf, health *health.Tracker, knobs *controlk
ret.nrptDB = newNRPTRuleDatabase(logf)
}
var err error
if ret.unregisterPolicyChangeCb, err = syspolicy.RegisterChangeCallback(ret.sysPolicyChanged); err != nil {
logf("error registering policy change callback: %v", err) // non-fatal
}
go func() {
// Log WSL status once at startup.
if distros, err := wslDistros(); err != nil {
@@ -362,11 +372,9 @@ func (m *windowsManager) SetDNS(cfg OSConfig) error {
// configuration only, routing one set of things to the "split"
// resolver and the rest to the primary.
// Unconditionally disable dynamic DNS updates and NetBIOS on our
// interfaces.
if err := m.disableDynamicUpdates(); err != nil {
m.logf("disableDynamicUpdates error: %v\n", err)
}
// Reconfigure DNS registration according to the [syspolicy.DNSRegistration]
// policy setting, and unconditionally disable NetBIOS on our interfaces.
m.reconfigureDNSRegistration()
if err := m.disableNetBIOS(); err != nil {
m.logf("disableNetBIOS error: %v\n", err)
}
@@ -485,6 +493,10 @@ func (m *windowsManager) Close() error {
m.closing = true
m.mu.Unlock()
if m.unregisterPolicyChangeCb != nil {
m.unregisterPolicyChangeCb()
}
err := m.SetDNS(OSConfig{})
if m.nrptDB != nil {
m.nrptDB.Close()
@@ -493,15 +505,62 @@ func (m *windowsManager) Close() error {
return err
}
// disableDynamicUpdates sets the appropriate registry values to prevent the
// Windows DHCP client from sending dynamic DNS updates for our interface to
// AD domain controllers.
func (m *windowsManager) disableDynamicUpdates() error {
// sysPolicyChanged is a callback triggered by [syspolicy] when it detects
// a change in one or more syspolicy settings.
func (m *windowsManager) sysPolicyChanged(policy *rsop.PolicyChange) {
if policy.HasChanged(syspolicy.EnableDNSRegistration) {
m.reconfigureDNSRegistration()
}
}
// reconfigureDNSRegistration configures the DNS registration settings
// using the [syspolicy.DNSRegistration] policy setting, if it is set.
// If the policy is not configured, it disables DNS registration.
func (m *windowsManager) reconfigureDNSRegistration() {
// Disable DNS registration by default (if the policy setting is not configured).
// This is primarily for historical reasons and to avoid breaking existing
// setups that rely on this behavior.
enableDNSRegistration, err := syspolicy.GetPreferenceOptionOrDefault(syspolicy.EnableDNSRegistration, setting.NeverByPolicy)
if err != nil {
m.logf("error getting DNSRegistration policy setting: %v", err) // non-fatal; we'll use the default
}
if enableDNSRegistration.Show() {
// "Show" reports whether the policy setting is configured as "user-decides".
// The name is a bit unfortunate in this context, as we don't actually "show" anything.
// Still, if the admin configured the policy as "user-decides", we shouldn't modify
// the adapter's settings and should leave them up to the user (admin rights required)
// or the system defaults.
return
}
// Otherwise, if the policy setting is configured as "always" or "never",
// we should configure the adapter accordingly.
if err := m.configureDNSRegistration(enableDNSRegistration.IsAlways()); err != nil {
m.logf("error configuring DNS registration: %v", err)
}
}
// configureDNSRegistration sets the appropriate registry values to allow or prevent
// the Windows DHCP client from registering Tailscale IP addresses with DNS
// and sending dynamic updates for our interface to AD domain controllers.
func (m *windowsManager) configureDNSRegistration(enabled bool) error {
prefixen := []winutil.RegistryPathPrefix{
winutil.IPv4TCPIPInterfacePrefix,
winutil.IPv6TCPIPInterfacePrefix,
}
var (
registrationEnabled = uint32(0)
disableDynamicUpdate = uint32(1)
maxNumberOfAddressesToRegister = uint32(0)
)
if enabled {
registrationEnabled = 1
disableDynamicUpdate = 0
maxNumberOfAddressesToRegister = 1
}
for _, prefix := range prefixen {
k, err := m.openInterfaceKey(prefix)
if err != nil {
@@ -509,13 +568,13 @@ func (m *windowsManager) disableDynamicUpdates() error {
}
defer k.Close()
if err := k.SetDWordValue("RegistrationEnabled", 0); err != nil {
if err := k.SetDWordValue("RegistrationEnabled", registrationEnabled); err != nil {
return err
}
if err := k.SetDWordValue("DisableDynamicUpdate", 1); err != nil {
if err := k.SetDWordValue("DisableDynamicUpdate", disableDynamicUpdate); err != nil {
return err
}
if err := k.SetDWordValue("MaxNumberOfAddressesToRegister", 0); err != nil {
if err := k.SetDWordValue("MaxNumberOfAddressesToRegister", maxNumberOfAddressesToRegister); err != nil {
return err
}
}