net/dns: restore SELinux context on /etc/resolv.conf after rename (#20167)
In direct mode we write resolv.conf via a temp file and rename(2), which preserves the source's generic etc_t label instead of net_conf_t, causing AVC denials when NetworkManager later manages the file. Run restorecon after the rename (Linux, SELinux-enforcing, best effort) to restore the policy-default label. Fixes #20149 Signed-off-by: Brendan Creane <bcreane@gmail.com>
This commit is contained in:
@@ -26,8 +26,10 @@ import (
|
|||||||
|
|
||||||
"tailscale.com/feature"
|
"tailscale.com/feature"
|
||||||
"tailscale.com/health"
|
"tailscale.com/health"
|
||||||
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/net/dns/resolvconffile"
|
"tailscale.com/net/dns/resolvconffile"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
|
"tailscale.com/types/lazy"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/dnsname"
|
"tailscale.com/util/dnsname"
|
||||||
"tailscale.com/util/eventbus"
|
"tailscale.com/util/eventbus"
|
||||||
@@ -277,6 +279,9 @@ func (m *directManager) rename(old, new string) error {
|
|||||||
if !m.renameBroken {
|
if !m.renameBroken {
|
||||||
err := m.fs.Rename(old, new)
|
err := m.fs.Rename(old, new)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if new == resolvConf {
|
||||||
|
m.relabelResolvConf()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "linux" && distro.Get() == distro.Synology {
|
if runtime.GOOS == "linux" && distro.Get() == distro.Synology {
|
||||||
@@ -309,9 +314,55 @@ func (m *directManager) rename(old, new string) error {
|
|||||||
return fmt.Errorf("remove of %q failed (%w) and so did truncate: %v", old, err, err2)
|
return fmt.Errorf("remove of %q failed (%w) and so did truncate: %v", old, err, err2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if new == resolvConf {
|
||||||
|
m.relabelResolvConf()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var restoreconPath lazy.SyncValue[string] // path to restorecon, or "" if absent
|
||||||
|
|
||||||
|
// relabelResolvConf restores the policy-default SELinux context on
|
||||||
|
// /etc/resolv.conf. Best effort: only runs when SELinux is enforcing. See:
|
||||||
|
//
|
||||||
|
// https://github.com/tailscale/tailscale/issues/20149.
|
||||||
|
func (m *directManager) relabelResolvConf() {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
restorecon := restoreconPath.Get(func() string {
|
||||||
|
p, _ := exec.LookPath("restorecon")
|
||||||
|
return p
|
||||||
|
})
|
||||||
|
if restorecon == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !hostinfo.IsSELinuxEnforcing() {
|
||||||
|
// Clear any prior warning, e.g. if SELinux was turned off at runtime
|
||||||
|
// with setenforce(8) since the last failure.
|
||||||
|
m.health.SetHealthy(selinuxRelabelWarnable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
// m.fs may be rooted at a test prefix; restorecon needs the real path.
|
||||||
|
actualPath := m.fs.ActualPath(resolvConf)
|
||||||
|
if out, err := exec.CommandContext(ctx, restorecon, actualPath).CombinedOutput(); err != nil {
|
||||||
|
m.logf("dns: restorecon of %q failed: %v, output: %s", actualPath, err, bytes.TrimSpace(out))
|
||||||
|
m.health.SetUnhealthy(selinuxRelabelWarnable, nil)
|
||||||
|
} else {
|
||||||
|
m.health.SetHealthy(selinuxRelabelWarnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selinuxRelabelWarnable = health.Register(&health.Warnable{
|
||||||
|
Code: "resolv-conf-relabel-failed",
|
||||||
|
Severity: health.SeverityMedium,
|
||||||
|
Title: "DNS configuration issue",
|
||||||
|
Text: health.StaticMessage("Failed to restore the SELinux label on /etc/resolv.conf; DNS may break when other services manage the file. See https://github.com/tailscale/tailscale/issues/20149"),
|
||||||
|
})
|
||||||
|
|
||||||
// setWant sets the expected contents of /etc/resolv.conf, if any.
|
// setWant sets the expected contents of /etc/resolv.conf, if any.
|
||||||
//
|
//
|
||||||
// A value of nil means no particular value is expected.
|
// A value of nil means no particular value is expected.
|
||||||
|
|||||||
Reference in New Issue
Block a user