kube-proxy: only set route_localnet if required

kube-proxy sets the sysctl net.ipv4.conf.all.route_localnet=1
so NodePort services can be accessed on the loopback addresses in
IPv4, but this may present security issues.

Leverage the --nodeport-addresses flag to opt-out of this feature,
if the list is not empty and none of the IP ranges contains an IPv4
loopback address this sysctl is not set.

In addition, add a warning to inform users about this behavior.
This commit is contained in:
Antonio Ojea 2022-01-21 11:27:55 +01:00
parent c175418281
commit 8b5fa408e0
3 changed files with 102 additions and 3 deletions

View File

@ -264,9 +264,12 @@ func NewProxier(ipt utiliptables.Interface,
healthzServer healthcheck.ProxierHealthUpdater,
nodePortAddresses []string,
) (*Proxier, error) {
// Set the route_localnet sysctl we need for
if err := utilproxy.EnsureSysctl(sysctl, sysctlRouteLocalnet, 1); err != nil {
return nil, err
if utilproxy.ContainsIPv4Loopback(nodePortAddresses) {
// Set the route_localnet sysctl we need for exposing NodePorts on loopback addresses
klog.InfoS("Setting route_localnet=1, use nodePortAddresses to filter loopback addresses for NodePorts to skip it https://issues.k8s.io/90259")
if err := utilproxy.EnsureSysctl(sysctl, sysctlRouteLocalnet, 1); err != nil {
return nil, err
}
}
// Proxy needs br_netfilter and bridge-nf-call-iptables=1 when containers

View File

@ -78,6 +78,37 @@ func BuildPortsToEndpointsMap(endpoints *v1.Endpoints) map[string][]string {
return portsToEndpoints
}
// ContainsIPv4Loopback returns true if the input is empty or one of the CIDR contains an IPv4 loopback address.
func ContainsIPv4Loopback(cidrStrings []string) bool {
if len(cidrStrings) == 0 {
return true
}
// RFC 5735 127.0.0.0/8 - This block is assigned for use as the Internet host loopback address
ipv4LoopbackStart := netutils.ParseIPSloppy("127.0.0.0")
for _, cidr := range cidrStrings {
if IsZeroCIDR(cidr) {
return true
}
ip, ipnet, err := netutils.ParseCIDRSloppy(cidr)
if err != nil {
continue
}
if netutils.IsIPv6CIDR(ipnet) {
continue
}
if ip.IsLoopback() {
return true
}
if ipnet.Contains(ipv4LoopbackStart) {
return true
}
}
return false
}
// IsZeroCIDR checks whether the input CIDR string is either
// the IPv4 or IPv6 zero CIDR
func IsZeroCIDR(cidr string) bool {

View File

@ -1401,3 +1401,68 @@ func TestAddressSet(t *testing.T) {
}
}
}
func TestContainsIPv4Loopback(t *testing.T) {
tests := []struct {
name string
cidrStrings []string
want bool
}{
{
name: "empty",
want: true,
},
{
name: "all zeros ipv4",
cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "0.0.0.0/0"},
want: true,
},
{
name: "all zeros ipv4 and invalid cidr",
cidrStrings: []string{"invalid.cidr", "192.168.0.0/16", "fd00:1:d::/64", "0.0.0.0/0"},
want: true,
},
{
name: "all zeros ipv6", // interpret all zeros equal for IPv4 and IPv6 as Golang stdlib
cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "::/0"},
want: true,
},
{
name: "ipv4 loopback",
cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "127.0.0.0/8"},
want: true,
},
{
name: "ipv6 loopback",
cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "::1/128"},
want: false,
},
{
name: "ipv4 loopback smaller range",
cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "127.0.2.0/28"},
want: true,
},
{
name: "ipv4 loopback within larger range",
cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "64.0.0.0/2"},
want: true,
},
{
name: "non loop loopback",
cidrStrings: []string{"128.0.2.0/28", "224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64"},
want: false,
},
{
name: "invalid cidr",
cidrStrings: []string{"invalid.ip/invalid.mask"},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ContainsIPv4Loopback(tt.cidrStrings); got != tt.want {
t.Errorf("ContainLoopback() = %v, want %v", got, tt.want)
}
})
}
}