Skip to content

Commit

Permalink
Add network disjointness check between shoot node network and seed po…
Browse files Browse the repository at this point in the history
…d network. (gardener#8353)

* Add network disjointness check between shoot node network and seed pod network.

In case there is an overlap between the shoot node network and the seed pod network
there can be strange network issues due to incorrect layer 3 routing. For the default
VPN, the affected connections are from kube-apiserver to vpn-seed-server and
from istio-ingressgateway to vpn-seed-server. In both cases the reply (SYN-ACK) may
be incorrectly sent into the VPN tunnel when the source pod IP overlaps with the
shoot node network as the shoot node network is added as a special route along with
the shoot service and shoot pod networks.
In the highly-available VPN, the affected connections are from any pod in the seed to
kube-apiserver of the shoot cluster. SYN-ACK packets may be sent incorrectly into the
VPN tunnel as kube-apiserver has similar special route for shoot nodes, pods and
services.
This issue is not new, but was present also with the previous VPN solution.

* Address review feedback
  • Loading branch information
ScheererJ authored Aug 16, 2023
1 parent 3a2e7d3 commit ce973d6
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 45 deletions.
61 changes: 19 additions & 42 deletions pkg/utils/validation/cidr/disjoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,56 +24,33 @@ import (

// ValidateNetworkDisjointedness validates that the given <seedNetworks> and <k8sNetworks> are disjoint.
func ValidateNetworkDisjointedness(fldPath *field.Path, shootNodes, shootPods, shootServices, seedNodes *string, seedPods, seedServices string, workerless bool) field.ErrorList {
var (
allErrs = field.ErrorList{}
allErrs := field.ErrorList{}

pathNodes = fldPath.Child("nodes")
pathServices = fldPath.Child("services")
pathPods = fldPath.Child("pods")
)
allErrs = append(allErrs, validateOverlapWithSeed(fldPath.Child("nodes"), shootNodes, "node", false, seedNodes, seedPods, seedServices)...)
allErrs = append(allErrs, validateOverlapWithSeed(fldPath.Child("services"), shootServices, "service", true, seedNodes, seedPods, seedServices)...)
allErrs = append(allErrs, validateOverlapWithSeed(fldPath.Child("pods"), shootPods, "pod", !workerless, seedNodes, seedPods, seedServices)...)

if shootNodes != nil && seedNodes != nil && NetworksIntersect(*shootNodes, *seedNodes) {
allErrs = append(allErrs, field.Invalid(pathNodes, *shootNodes, "shoot node network intersects with seed node network"))
}
if shootNodes != nil && NetworksIntersect(*shootNodes, seedServices) {
allErrs = append(allErrs, field.Invalid(pathNodes, *shootNodes, "shoot node network intersects with seed service network"))
}
if shootNodes != nil && NetworksIntersect(*shootNodes, v1beta1constants.DefaultVPNRange) {
allErrs = append(allErrs, field.Invalid(pathNodes, *shootNodes, fmt.Sprintf("shoot node network intersects with default vpn network (%s)", v1beta1constants.DefaultVPNRange)))
}
return allErrs
}

if shootServices != nil {
if NetworksIntersect(seedServices, *shootServices) {
allErrs = append(allErrs, field.Invalid(pathServices, *shootServices, "shoot service network intersects with seed service network"))
}
if NetworksIntersect(seedPods, *shootServices) {
allErrs = append(allErrs, field.Invalid(pathServices, *shootServices, "shoot service network intersects with seed pod network"))
}
if seedNodes != nil && NetworksIntersect(*seedNodes, *shootServices) {
allErrs = append(allErrs, field.Invalid(pathServices, *seedNodes, "shoot service network intersects with seed node network"))
}
if NetworksIntersect(v1beta1constants.DefaultVPNRange, *shootServices) {
allErrs = append(allErrs, field.Invalid(pathServices, *shootServices, fmt.Sprintf("shoot service network intersects with default vpn network (%s)", v1beta1constants.DefaultVPNRange)))
}
} else {
allErrs = append(allErrs, field.Required(pathServices, "services is required"))
}
func validateOverlapWithSeed(fldPath *field.Path, shootNetwork *string, networkType string, networkRequired bool, seedNodes *string, seedPods, seedServices string) field.ErrorList {
allErrs := field.ErrorList{}

if shootPods != nil {
if NetworksIntersect(seedPods, *shootPods) {
allErrs = append(allErrs, field.Invalid(pathPods, *shootPods, "shoot pod network intersects with seed pod network"))
if shootNetwork != nil {
if NetworksIntersect(seedServices, *shootNetwork) {
allErrs = append(allErrs, field.Invalid(fldPath, *shootNetwork, fmt.Sprintf("shoot %s network intersects with seed service network", networkType)))
}
if NetworksIntersect(seedServices, *shootPods) {
allErrs = append(allErrs, field.Invalid(pathPods, *shootPods, "shoot pod network intersects with seed service network"))
if NetworksIntersect(seedPods, *shootNetwork) {
allErrs = append(allErrs, field.Invalid(fldPath, *shootNetwork, fmt.Sprintf("shoot %s network intersects with seed pod network", networkType)))
}
if seedNodes != nil && NetworksIntersect(*seedNodes, *shootPods) {
allErrs = append(allErrs, field.Invalid(pathPods, *seedNodes, "shoot pod network intersects with seed node network"))
if seedNodes != nil && NetworksIntersect(*seedNodes, *shootNetwork) {
allErrs = append(allErrs, field.Invalid(fldPath, *shootNetwork, fmt.Sprintf("shoot %s network intersects with seed node network", networkType)))
}
if NetworksIntersect(v1beta1constants.DefaultVPNRange, *shootPods) {
allErrs = append(allErrs, field.Invalid(pathPods, *shootPods, fmt.Sprintf("shoot pod network intersects with default vpn network (%s)", v1beta1constants.DefaultVPNRange)))
if NetworksIntersect(v1beta1constants.DefaultVPNRange, *shootNetwork) {
allErrs = append(allErrs, field.Invalid(fldPath, *shootNetwork, fmt.Sprintf("shoot %s network intersects with default vpn network (%s)", networkType, v1beta1constants.DefaultVPNRange)))
}
} else if !workerless {
allErrs = append(allErrs, field.Required(pathPods, "pods is required"))
} else if networkRequired {
allErrs = append(allErrs, field.Required(fldPath, fmt.Sprintf("%ss is required", networkType)))
}

return allErrs
Expand Down
56 changes: 53 additions & 3 deletions pkg/utils/validation/cidr/disjoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ var _ = Describe("utils", func() {
var (
podsCIDR = "10.242.128.0/17"
servicesCIDR = "10.242.0.0/17"
nodesCIDR = "10.241.0.0/16"
nodesCIDR = "10.241.0.0/17"
)

errorList := ValidateNetworkDisjointedness(
Expand All @@ -277,6 +277,31 @@ var _ = Describe("utils", func() {
"Field": Equal("[].nodes"),
}))))
})

It("should fail due to seed pod network and shoot node network overlap", func() {
var (
podsCIDR = "10.242.128.0/17"
servicesCIDR = "10.242.0.0/17"
nodesCIDR = seedPodsCIDR
)

errorList := ValidateNetworkDisjointedness(
field.NewPath(""),
&nodesCIDR,
&podsCIDR,
&servicesCIDR,
&seedNodesCIDR,
seedPodsCIDR,
seedServicesCIDR,
false,
)

Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{
"Type": Equal(field.ErrorTypeInvalid),
"Field": Equal("[].nodes"),
"Detail": Equal("shoot node network intersects with seed pod network"),
}))))
})
})

Describe("#ValidateNetworkDisjointedness IPv6", func() {
Expand Down Expand Up @@ -341,7 +366,7 @@ var _ = Describe("utils", func() {
var (
podsCIDR = seedPodsCIDRIPv6
servicesCIDR = seedServicesCIDRIPv6
nodesCIDR = "2001:0db8:65a3::/113"
nodesCIDR = "2001:0db8:55a3::/112"
)

errorList := ValidateNetworkDisjointedness(
Expand Down Expand Up @@ -369,7 +394,7 @@ var _ = Describe("utils", func() {
var (
podsCIDR = seedNodesCIDRIPv6
servicesCIDR = seedNodesCIDRIPv6
nodesCIDR = "2001:0db8:65a3::/113"
nodesCIDR = "2001:0db8:55a3::/112"
)

errorList := ValidateNetworkDisjointedness(
Expand All @@ -392,6 +417,31 @@ var _ = Describe("utils", func() {
})),
))
})

It("should fail due to seed pod network and shoot node network overlap", func() {
var (
podsCIDR = "2001:0db8:35a3::/113"
servicesCIDR = "2001:0db8:45a3::/113"
nodesCIDR = seedPodsCIDRIPv6
)

errorList := ValidateNetworkDisjointedness(
field.NewPath(""),
&nodesCIDR,
&podsCIDR,
&servicesCIDR,
&seedNodesCIDRIPv6,
seedPodsCIDRIPv6,
seedServicesCIDRIPv6,
false,
)

Expect(errorList).To(ConsistOf(PointTo(MatchFields(IgnoreExtras, Fields{
"Type": Equal(field.ErrorTypeInvalid),
"Field": Equal("[].nodes"),
"Detail": Equal("shoot node network intersects with seed pod network"),
}))))
})
})

Describe("#ValidateShootNetworkDisjointedness IPv4", func() {
Expand Down

0 comments on commit ce973d6

Please sign in to comment.