mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-11 22:49:54 +00:00
[vnet] Skip IPv6 setup when IPv6 is disabled on the host (#68368)
* skip IPv6 setup on Windows and Linux when IPv6 is disabled on host * reapply DNS nameservers on Linux when they change
This commit is contained in:
@@ -54,7 +54,7 @@ func runUnixAdminProcess(ctx context.Context, clt *clientApplicationServiceClien
|
||||
return trace.Wrap(err, "reporting network stack info to client application")
|
||||
}
|
||||
|
||||
osConfigProvider, err := newOSConfigProvider(osConfigProviderConfig{
|
||||
osConfigProvider, err := newOSConfigProvider(ctx, osConfigProviderConfig{
|
||||
clt: clt,
|
||||
tunName: tunName,
|
||||
ipv6Prefix: networkStackConfig.ipv6Prefix.String(),
|
||||
|
||||
@@ -116,7 +116,7 @@ func runWindowsAdminProcess(ctx context.Context, cfg *windowsAdminProcessConfig)
|
||||
return trace.Wrap(err, "reporting network stack info to client application")
|
||||
}
|
||||
|
||||
osConfigProvider, err := newOSConfigProvider(osConfigProviderConfig{
|
||||
osConfigProvider, err := newOSConfigProvider(ctx, osConfigProviderConfig{
|
||||
clt: clt,
|
||||
tunName: tunName,
|
||||
ipv6Prefix: networkStackConfig.ipv6Prefix.String(),
|
||||
|
||||
@@ -160,7 +160,7 @@ func (vnet *EmbeddedVNet) Run(ctx context.Context) error {
|
||||
return trace.Wrap(err, "getting TUN device name")
|
||||
}
|
||||
|
||||
osConfigProvider, err := newOSConfigProvider(osConfigProviderConfig{
|
||||
osConfigProvider, err := newOSConfigProvider(ctx, osConfigProviderConfig{
|
||||
clt: vnet.client,
|
||||
tunName: tunName,
|
||||
ipv6Prefix: stackConfig.ipv6Prefix.String(),
|
||||
@@ -176,10 +176,14 @@ func (vnet *EmbeddedVNet) Run(ctx context.Context) error {
|
||||
if oc.tunName == "" {
|
||||
return vnet.configureHost(ctx, nil)
|
||||
}
|
||||
cidrRanges := oc.cidrRanges
|
||||
if oc.tunIPv6 != "" {
|
||||
cidrRanges = append(cidrRanges, oc.tunIPv6+"/64")
|
||||
}
|
||||
return vnet.configureHost(ctx, &EmbeddedVNetHostConfig{
|
||||
DeviceIPv4: oc.tunIPv4,
|
||||
DeviceIPv6: oc.tunIPv6,
|
||||
CIDRRanges: append(oc.cidrRanges, oc.tunIPv6+"/64"),
|
||||
CIDRRanges: cidrRanges,
|
||||
DNSAddrs: oc.dnsAddrs,
|
||||
DNSZones: oc.dnsZones,
|
||||
})
|
||||
|
||||
@@ -107,6 +107,12 @@ func platformConfigureOS(ctx context.Context, cfg *osConfig, state *platformOSCo
|
||||
return nil
|
||||
}
|
||||
|
||||
// hostIPv6Disabled always returns false on macOS, there is no system-wide way
|
||||
// to disable IPv6 on macOS, it can only be turned off per link.
|
||||
func hostIPv6Disabled(_ /*tunName*/ string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
const resolverFileComment = "# automatically installed by Teleport VNet"
|
||||
|
||||
var resolverPath = filepath.Join("/", "etc", "resolver")
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -34,7 +36,7 @@ type platformOSConfigState struct {
|
||||
configuredIPv6 bool
|
||||
configuredIPv4 bool
|
||||
configuredCidrRanges []string
|
||||
configuredNameserver bool
|
||||
configuredDNSAddrs []string
|
||||
configuredDNSZones []string
|
||||
broughtUpInterface bool
|
||||
tunName string
|
||||
@@ -66,7 +68,7 @@ func platformConfigureOS(ctx context.Context, cfg *osConfig, state *platformOSCo
|
||||
if err := configureDNS(ctx, cfg, state); err != nil {
|
||||
return trace.Wrap(err, "configuring DNS")
|
||||
}
|
||||
if (state.configuredIPv4 || state.configuredIPv6) && state.configuredNameserver && !state.broughtUpInterface {
|
||||
if (state.configuredIPv4 || state.configuredIPv6) && len(state.configuredDNSAddrs) > 0 && !state.broughtUpInterface {
|
||||
log.InfoContext(ctx, "Bringing up the VNet interface", "device", cfg.tunName)
|
||||
if err := runCommand(ctx,
|
||||
"ip", "link", "set", cfg.tunName, "up",
|
||||
@@ -92,6 +94,34 @@ func platformConfigureOS(ctx context.Context, cfg *osConfig, state *platformOSCo
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// procIPv6Conf holds per-interface IPv6 settings, it is absent when the
|
||||
// kernel was booted with IPv6 disabled.
|
||||
procIPv6Conf = "/proc/sys/net/ipv6/conf"
|
||||
// disableIPv6Setting is the per-interface setting that disables IPv6 when set to 1.
|
||||
disableIPv6Setting = "disable_ipv6"
|
||||
)
|
||||
|
||||
// hostIPv6Disabled checks whether IPv6 has been disabled on the host.
|
||||
func hostIPv6Disabled(tunName string) (bool, error) {
|
||||
if _, err := os.Stat(procIPv6Conf); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return true, nil
|
||||
}
|
||||
return false, trace.Wrap(err, "checking existence of %s", procIPv6Conf)
|
||||
}
|
||||
path := filepath.Join(procIPv6Conf, tunName, disableIPv6Setting)
|
||||
disabled, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
return false, trace.Wrap(err, "reading %s", path)
|
||||
}
|
||||
// The file contains "1" when IPv6 is disabled on the device, "0" when enabled
|
||||
return strings.TrimSpace(string(disabled)) == "1", nil
|
||||
}
|
||||
|
||||
func shouldReconfigureDNSZones(cfg *osConfig, state *platformOSConfigState) bool {
|
||||
return !utils.ContainSameUniqueElements(cfg.dnsZones, state.configuredDNSZones)
|
||||
}
|
||||
@@ -148,7 +178,7 @@ func configureDNS(ctx context.Context, cfg *osConfig, state *platformOSConfigSta
|
||||
state.configuredDNSZones = cfg.dnsZones
|
||||
}
|
||||
|
||||
if len(cfg.dnsAddrs) > 0 && state.tunName != "" && !state.configuredNameserver {
|
||||
if len(cfg.dnsAddrs) > 0 && state.tunName != "" && !slices.Equal(cfg.dnsAddrs, state.configuredDNSAddrs) {
|
||||
iface, err := net.InterfaceByName(state.tunName)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "looking up interface %s", state.tunName)
|
||||
@@ -171,7 +201,7 @@ func configureDNS(ctx context.Context, cfg *osConfig, state *platformOSConfigSta
|
||||
if err := systemdresolved.SetLinkDNS(ctx, conn, int32(iface.Index), addresses); err != nil {
|
||||
return err
|
||||
}
|
||||
state.configuredNameserver = true
|
||||
state.configuredDNSAddrs = cfg.dnsAddrs
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -43,13 +43,31 @@ type osConfigProviderConfig struct {
|
||||
ipv6Prefix string
|
||||
dnsIPv6 string
|
||||
addDNSAddress func(net.IP) error
|
||||
// checkHostIPv6Disabled reports whether IPv6 is disabled host-wide.
|
||||
// Defaults to the platform-specific hostIPv6Disabled, overridable in tests.
|
||||
checkHostIPv6Disabled func(tunName string) (bool, error)
|
||||
}
|
||||
|
||||
type targetOSConfigGetter interface {
|
||||
GetTargetOSConfiguration(context.Context) (*vnetv1.TargetOSConfiguration, error)
|
||||
}
|
||||
|
||||
func newOSConfigProvider(cfg osConfigProviderConfig) (*osConfigProvider, error) {
|
||||
func newOSConfigProvider(ctx context.Context, cfg osConfigProviderConfig) (*osConfigProvider, error) {
|
||||
checkHostIPv6Disabled := cfg.checkHostIPv6Disabled
|
||||
if checkHostIPv6Disabled == nil {
|
||||
checkHostIPv6Disabled = hostIPv6Disabled
|
||||
}
|
||||
ipv6Disabled, err := checkHostIPv6Disabled(cfg.tunName)
|
||||
if err != nil {
|
||||
// Could not determine, assume IPv6 is enabled. If it is actually
|
||||
// disabled, the address assignment will fail and the error will
|
||||
// surface there.
|
||||
log.WarnContext(ctx, "Failed to check whether IPv6 is disabled on the host, assuming it is enabled.",
|
||||
"error", err)
|
||||
} else if ipv6Disabled {
|
||||
log.WarnContext(ctx, "IPv6 is disabled on this host, VNet will skip IPv6 configuration and work over IPv4 only.")
|
||||
return &osConfigProvider{cfg: cfg}, nil
|
||||
}
|
||||
tunIPv6, err := tunIPv6ForPrefix(cfg.ipv6Prefix)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
|
||||
@@ -35,9 +35,11 @@ func TestOSConfigProvider(t *testing.T) {
|
||||
dnsIPv6 string
|
||||
dnsZones []string
|
||||
ipv4CIDRRanges []string
|
||||
hostIPv6Disabled bool
|
||||
getTargetOSConfigErr error
|
||||
expectErr error
|
||||
expectTargetOSConfig *osConfig
|
||||
expectAddedDNSAddrs []string
|
||||
}{
|
||||
{
|
||||
// No IPv4 address should be assigned until an IPv4 CIDR range is
|
||||
@@ -73,6 +75,7 @@ func TestOSConfigProvider(t *testing.T) {
|
||||
dnsZones: []string{"test.example.com"},
|
||||
cidrRanges: []string{"192.168.1.0/24"},
|
||||
},
|
||||
expectAddedDNSAddrs: []string{"192.168.1.2"},
|
||||
},
|
||||
{
|
||||
desc: "multiple cidr ranges",
|
||||
@@ -91,6 +94,39 @@ func TestOSConfigProvider(t *testing.T) {
|
||||
dnsZones: []string{"test.example.com"},
|
||||
cidrRanges: []string{"10.64.0.0/10", "192.168.1.0/24"},
|
||||
},
|
||||
expectAddedDNSAddrs: []string{"10.64.0.2"},
|
||||
},
|
||||
{
|
||||
// With IPv6 disabled on the host and no logged-in clusters, the
|
||||
// target OS config should be empty
|
||||
desc: "ipv6 disabled, no cidr ranges",
|
||||
tunName: "testtun1",
|
||||
ipv6Prefix: "fd01:2345:6789::",
|
||||
dnsIPv6: "fd01:2345:6789::2",
|
||||
hostIPv6Disabled: true,
|
||||
expectTargetOSConfig: &osConfig{
|
||||
tunName: "testtun1",
|
||||
},
|
||||
},
|
||||
{
|
||||
// With IPv6 disabled on the host, only the IPv4 address and the
|
||||
// IPv4 nameserver should be configured.
|
||||
desc: "ipv6 disabled, with cidr range",
|
||||
tunName: "testtun1",
|
||||
ipv6Prefix: "fd01:2345:6789::",
|
||||
dnsIPv6: "fd01:2345:6789::2",
|
||||
dnsZones: []string{"test.example.com"},
|
||||
ipv4CIDRRanges: []string{"192.168.1.0/24"},
|
||||
hostIPv6Disabled: true,
|
||||
expectTargetOSConfig: &osConfig{
|
||||
tunName: "testtun1",
|
||||
tunIPv4: "192.168.1.1",
|
||||
tunIPv4Net: &net.IPNet{IP: []byte{192, 168, 1, 0}, Mask: []byte{255, 255, 255, 0}},
|
||||
dnsAddrs: []string{"192.168.1.2"},
|
||||
dnsZones: []string{"test.example.com"},
|
||||
cidrRanges: []string{"192.168.1.0/24"},
|
||||
},
|
||||
expectAddedDNSAddrs: []string{"192.168.1.2"},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
@@ -103,7 +139,7 @@ func TestOSConfigProvider(t *testing.T) {
|
||||
}
|
||||
// Keep track of new DNS addresses the osConfigProvider tried to add.
|
||||
var addedDNSAddrs []string
|
||||
osConfigProvider, err := newOSConfigProvider(osConfigProviderConfig{
|
||||
osConfigProvider, err := newOSConfigProvider(ctx, osConfigProviderConfig{
|
||||
clt: targetOSConfigGetter,
|
||||
tunName: tc.tunName,
|
||||
ipv6Prefix: tc.ipv6Prefix,
|
||||
@@ -112,6 +148,9 @@ func TestOSConfigProvider(t *testing.T) {
|
||||
addedDNSAddrs = append(addedDNSAddrs, ip.String())
|
||||
return nil
|
||||
},
|
||||
checkHostIPv6Disabled: func(string) (bool, error) {
|
||||
return tc.hostIPv6Disabled, nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -122,12 +161,7 @@ func TestOSConfigProvider(t *testing.T) {
|
||||
}
|
||||
require.Equal(t, tc.expectTargetOSConfig, targetOSConfig)
|
||||
|
||||
// expectTargetOSConfig.dnsAddrs always starts with the IPv6 DNS
|
||||
// addr, assert that any additional addrs were added to the network
|
||||
// stack.
|
||||
if len(tc.expectTargetOSConfig.dnsAddrs) > 1 {
|
||||
require.ElementsMatch(t, tc.expectTargetOSConfig.dnsAddrs[1:], addedDNSAddrs)
|
||||
}
|
||||
require.ElementsMatch(t, tc.expectAddedDNSAddrs, addedDNSAddrs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,9 @@ const (
|
||||
// The UUID at the end was randomly generated once, VNet
|
||||
// always writes policies at this key and cleans it up on shutdown.
|
||||
vnetNRPTKeyID = `{ad074e9a-bd1b-447e-9108-14e545bf11a5}`
|
||||
|
||||
// This key holds host IPv6 configuration.
|
||||
tcpip6ParametersKey = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters`
|
||||
)
|
||||
|
||||
func configureDNS(ctx context.Context, zones, nameservers []string, doesGroupPolicyKeyExist bool) error {
|
||||
@@ -217,6 +220,37 @@ func configureDNS(ctx context.Context, zones, nameservers []string, doesGroupPol
|
||||
return nil
|
||||
}
|
||||
|
||||
// hostIPv6Disabled checks whether IPv6 has been disabled on the host via the
|
||||
// DisabledComponents value under tcpip6ParametersKey. The setting is
|
||||
// host-wide, so the TUN device name is unused.
|
||||
func hostIPv6Disabled(_ /*tunName*/ string) (bool, error) {
|
||||
// Bit 0x10 disables IPv6 on all non-tunnel interfaces, checking it also
|
||||
// covers the 0xFF value that disables IPv6 entirely. Absent key or value
|
||||
// means that IPv6 is enabled.
|
||||
//
|
||||
// "Tunnel" interfaces here refers to Microsoft's tunneling protocols (6to4/ISATAP/Teredo)
|
||||
// that encapsulate IPv6 packets inside IPv4. VNet's TUN device doesn't fall under this category.
|
||||
//
|
||||
// https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/configure-ipv6-in-windows#ipv6-tunnel-interfaces
|
||||
const disableIPv6NontunnelBit = 0x10
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, tcpip6ParametersKey, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
if errors.Is(err, registry.ErrNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
return false, trace.Wrap(err, "opening registry key %s", tcpip6ParametersKey)
|
||||
}
|
||||
defer key.Close()
|
||||
val, _, err := key.GetIntegerValue("DisabledComponents")
|
||||
if err != nil {
|
||||
if errors.Is(err, registry.ErrNotExist) {
|
||||
return false, nil
|
||||
}
|
||||
return false, trace.Wrap(err, "reading DisabledComponents registry value")
|
||||
}
|
||||
return val&disableIPv6NontunnelBit != 0, nil
|
||||
}
|
||||
|
||||
func doesKeyPathExist(k registry.Key, path string) (bool, error) {
|
||||
key, err := registry.OpenKey(k, path, registry.READ)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user