k8s.io/kubernetes@v1.29.3/test/e2e/network/firewall.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package network 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "time" 24 25 v1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/sets" 28 "k8s.io/apimachinery/pkg/util/wait" 29 clientset "k8s.io/client-go/kubernetes" 30 cloudprovider "k8s.io/cloud-provider" 31 "k8s.io/kubernetes/pkg/cluster/ports" 32 kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config" 33 "k8s.io/kubernetes/test/e2e/framework" 34 e2enetwork "k8s.io/kubernetes/test/e2e/framework/network" 35 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 36 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 37 "k8s.io/kubernetes/test/e2e/framework/providers/gce" 38 e2eservice "k8s.io/kubernetes/test/e2e/framework/service" 39 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 40 "k8s.io/kubernetes/test/e2e/network/common" 41 gcecloud "k8s.io/legacy-cloud-providers/gce" 42 admissionapi "k8s.io/pod-security-admission/api" 43 44 "github.com/onsi/ginkgo/v2" 45 ) 46 47 const ( 48 firewallTestTCPTimeout = time.Duration(1 * time.Second) 49 // Set ports outside of 30000-32767, 80 and 8080 to avoid being allowlisted by the e2e cluster 50 firewallTestHTTPPort = int32(29999) 51 firewallTestUDPPort = int32(29998) 52 ) 53 54 var _ = common.SIGDescribe("Firewall rule", func() { 55 var firewallTestName = "firewall-test" 56 f := framework.NewDefaultFramework(firewallTestName) 57 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 58 59 var cs clientset.Interface 60 var cloudConfig framework.CloudConfig 61 var gceCloud *gcecloud.Cloud 62 63 ginkgo.BeforeEach(func() { 64 e2eskipper.SkipUnlessProviderIs("gce") 65 66 var err error 67 cs = f.ClientSet 68 cloudConfig = framework.TestContext.CloudConfig 69 gceCloud, err = gce.GetGCECloud() 70 framework.ExpectNoError(err) 71 }) 72 73 // This test takes around 6 minutes to run 74 f.It(f.WithSlow(), f.WithSerial(), "should create valid firewall rules for LoadBalancer type service", func(ctx context.Context) { 75 ns := f.Namespace.Name 76 // This source ranges is just used to examine we have exact same things on LB firewall rules 77 firewallTestSourceRanges := []string{"0.0.0.0/1", "128.0.0.0/1"} 78 serviceName := "firewall-test-loadbalancer" 79 80 ginkgo.By("Getting cluster ID") 81 clusterID, err := gce.GetClusterID(ctx, cs) 82 framework.ExpectNoError(err) 83 framework.Logf("Got cluster ID: %v", clusterID) 84 85 jig := e2eservice.NewTestJig(cs, ns, serviceName) 86 nodeList, err := e2enode.GetBoundedReadySchedulableNodes(ctx, cs, e2eservice.MaxNodesForEndpointsTests) 87 framework.ExpectNoError(err) 88 89 nodesNames := []string{} 90 for _, node := range nodeList.Items { 91 nodesNames = append(nodesNames, node.Name) 92 } 93 nodesSet := sets.NewString(nodesNames...) 94 95 ginkgo.By("Creating a LoadBalancer type service with ExternalTrafficPolicy=Global") 96 svc, err := jig.CreateLoadBalancerService(ctx, e2eservice.GetServiceLoadBalancerCreationTimeout(ctx, cs), func(svc *v1.Service) { 97 svc.Spec.Ports = []v1.ServicePort{{Protocol: v1.ProtocolTCP, Port: firewallTestHTTPPort}} 98 svc.Spec.LoadBalancerSourceRanges = firewallTestSourceRanges 99 }) 100 framework.ExpectNoError(err) 101 defer func() { 102 _, err = jig.UpdateService(ctx, func(svc *v1.Service) { 103 svc.Spec.Type = v1.ServiceTypeNodePort 104 svc.Spec.LoadBalancerSourceRanges = nil 105 }) 106 framework.ExpectNoError(err) 107 err = cs.CoreV1().Services(svc.Namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{}) 108 framework.ExpectNoError(err) 109 ginkgo.By("Waiting for the local traffic health check firewall rule to be deleted") 110 localHCFwName := gce.MakeHealthCheckFirewallNameForLBService(clusterID, cloudprovider.DefaultLoadBalancerName(svc), false) 111 _, err := gce.WaitForFirewallRule(ctx, gceCloud, localHCFwName, false, e2eservice.LoadBalancerCleanupTimeout) 112 framework.ExpectNoError(err) 113 }() 114 svcExternalIP := svc.Status.LoadBalancer.Ingress[0].IP 115 116 ginkgo.By("Checking if service's firewall rule is correct") 117 lbFw := gce.ConstructFirewallForLBService(svc, cloudConfig.NodeTag) 118 fw, err := gceCloud.GetFirewall(lbFw.Name) 119 framework.ExpectNoError(err) 120 err = gce.VerifyFirewallRule(fw, lbFw, cloudConfig.Network, false) 121 framework.ExpectNoError(err) 122 123 ginkgo.By("Checking if service's nodes health check firewall rule is correct") 124 nodesHCFw := gce.ConstructHealthCheckFirewallForLBService(clusterID, svc, cloudConfig.NodeTag, true) 125 fw, err = gceCloud.GetFirewall(nodesHCFw.Name) 126 framework.ExpectNoError(err) 127 err = gce.VerifyFirewallRule(fw, nodesHCFw, cloudConfig.Network, false) 128 framework.ExpectNoError(err) 129 130 // OnlyLocal service is needed to examine which exact nodes the requests are being forwarded to by the Load Balancer on GCE 131 ginkgo.By("Updating LoadBalancer service to ExternalTrafficPolicy=Local") 132 svc, err = jig.UpdateService(ctx, func(svc *v1.Service) { 133 svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyLocal 134 }) 135 framework.ExpectNoError(err) 136 137 ginkgo.By("Waiting for the nodes health check firewall rule to be deleted") 138 _, err = gce.WaitForFirewallRule(ctx, gceCloud, nodesHCFw.Name, false, e2eservice.LoadBalancerCleanupTimeout) 139 framework.ExpectNoError(err) 140 141 ginkgo.By("Waiting for the correct local traffic health check firewall rule to be created") 142 localHCFw := gce.ConstructHealthCheckFirewallForLBService(clusterID, svc, cloudConfig.NodeTag, false) 143 fw, err = gce.WaitForFirewallRule(ctx, gceCloud, localHCFw.Name, true, e2eservice.GetServiceLoadBalancerCreationTimeout(ctx, cs)) 144 framework.ExpectNoError(err) 145 err = gce.VerifyFirewallRule(fw, localHCFw, cloudConfig.Network, false) 146 framework.ExpectNoError(err) 147 148 ginkgo.By(fmt.Sprintf("Creating netexec pods on at most %v nodes", e2eservice.MaxNodesForEndpointsTests)) 149 for i, nodeName := range nodesNames { 150 podName := fmt.Sprintf("netexec%v", i) 151 152 framework.Logf("Creating netexec pod %q on node %v in namespace %q", podName, nodeName, ns) 153 pod := e2epod.NewAgnhostPod(ns, podName, nil, nil, nil, 154 "netexec", 155 fmt.Sprintf("--http-port=%d", firewallTestHTTPPort), 156 fmt.Sprintf("--udp-port=%d", firewallTestUDPPort)) 157 pod.ObjectMeta.Labels = jig.Labels 158 nodeSelection := e2epod.NodeSelection{Name: nodeName} 159 e2epod.SetNodeSelection(&pod.Spec, nodeSelection) 160 pod.Spec.HostNetwork = true 161 _, err := cs.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{}) 162 framework.ExpectNoError(err) 163 framework.ExpectNoError(e2epod.WaitTimeoutForPodReadyInNamespace(ctx, f.ClientSet, podName, f.Namespace.Name, framework.PodStartTimeout)) 164 framework.Logf("Netexec pod %q in namespace %q running", podName, ns) 165 166 defer func() { 167 framework.Logf("Cleaning up the netexec pod: %v", podName) 168 err = cs.CoreV1().Pods(ns).Delete(ctx, podName, metav1.DeleteOptions{}) 169 framework.ExpectNoError(err) 170 }() 171 } 172 173 // Send requests from outside of the cluster because internal traffic is allowlisted 174 ginkgo.By("Accessing the external service ip from outside, all non-master nodes should be reached") 175 err = testHitNodesFromOutside(svcExternalIP, firewallTestHTTPPort, e2eservice.GetServiceLoadBalancerPropagationTimeout(ctx, cs), nodesSet) 176 framework.ExpectNoError(err) 177 178 // Check if there are overlapping tags on the firewall that extend beyond just the vms in our cluster 179 // by removing the tag on one vm and make sure it doesn't get any traffic. This is an imperfect 180 // simulation, we really want to check that traffic doesn't reach a vm outside the GKE cluster, but 181 // that's much harder to do in the current e2e framework. 182 ginkgo.By(fmt.Sprintf("Removing tags from one of the nodes: %v", nodesNames[0])) 183 nodesSet.Delete(nodesNames[0]) 184 // Instance could run in a different zone in multi-zone test. Figure out which zone 185 // it is in before proceeding. 186 zone := cloudConfig.Zone 187 if zoneInLabel, ok := nodeList.Items[0].Labels[v1.LabelFailureDomainBetaZone]; ok { 188 zone = zoneInLabel 189 } else if zoneInLabel, ok := nodeList.Items[0].Labels[v1.LabelTopologyZone]; ok { 190 zone = zoneInLabel 191 } 192 removedTags := gce.SetInstanceTags(cloudConfig, nodesNames[0], zone, []string{}) 193 defer func() { 194 ginkgo.By("Adding tags back to the node and wait till the traffic is recovered") 195 nodesSet.Insert(nodesNames[0]) 196 gce.SetInstanceTags(cloudConfig, nodesNames[0], zone, removedTags) 197 // Make sure traffic is recovered before exit 198 err = testHitNodesFromOutside(svcExternalIP, firewallTestHTTPPort, e2eservice.GetServiceLoadBalancerPropagationTimeout(ctx, cs), nodesSet) 199 framework.ExpectNoError(err) 200 }() 201 202 ginkgo.By("Accessing service through the external ip and examine got no response from the node without tags") 203 err = testHitNodesFromOutsideWithCount(svcExternalIP, firewallTestHTTPPort, e2eservice.GetServiceLoadBalancerPropagationTimeout(ctx, cs), nodesSet, 15) 204 framework.ExpectNoError(err) 205 }) 206 207 ginkgo.It("control plane should not expose well-known ports", func(ctx context.Context) { 208 nodes, err := e2enode.GetReadySchedulableNodes(ctx, cs) 209 framework.ExpectNoError(err) 210 211 ginkgo.By("Checking well known ports on master and nodes are not exposed externally") 212 nodeAddr := e2enode.FirstAddress(nodes, v1.NodeExternalIP) 213 if nodeAddr != "" { 214 assertNotReachableHTTPTimeout(nodeAddr, "/", ports.KubeletPort, firewallTestTCPTimeout, false) 215 assertNotReachableHTTPTimeout(nodeAddr, "/", ports.KubeletReadOnlyPort, firewallTestTCPTimeout, false) 216 assertNotReachableHTTPTimeout(nodeAddr, "/", ports.ProxyStatusPort, firewallTestTCPTimeout, false) 217 } 218 219 controlPlaneAddresses := framework.GetControlPlaneAddresses(ctx, cs) 220 for _, instanceAddress := range controlPlaneAddresses { 221 assertNotReachableHTTPTimeout(instanceAddress, "/healthz", ports.KubeControllerManagerPort, firewallTestTCPTimeout, true) 222 assertNotReachableHTTPTimeout(instanceAddress, "/healthz", kubeschedulerconfig.DefaultKubeSchedulerPort, firewallTestTCPTimeout, true) 223 } 224 }) 225 }) 226 227 func assertNotReachableHTTPTimeout(ip, path string, port int, timeout time.Duration, enableHTTPS bool) { 228 result := e2enetwork.PokeHTTP(ip, port, path, &e2enetwork.HTTPPokeParams{Timeout: timeout, EnableHTTPS: enableHTTPS}) 229 if result.Status == e2enetwork.HTTPError { 230 framework.Failf("Unexpected error checking for reachability of %s:%d: %v", ip, port, result.Error) 231 } 232 if result.Code != 0 { 233 framework.Failf("Was unexpectedly able to reach %s:%d", ip, port) 234 } 235 } 236 237 // testHitNodesFromOutside checks HTTP connectivity from outside. 238 func testHitNodesFromOutside(externalIP string, httpPort int32, timeout time.Duration, expectedHosts sets.String) error { 239 return testHitNodesFromOutsideWithCount(externalIP, httpPort, timeout, expectedHosts, 1) 240 } 241 242 // testHitNodesFromOutsideWithCount checks HTTP connectivity from outside with count. 243 func testHitNodesFromOutsideWithCount(externalIP string, httpPort int32, timeout time.Duration, expectedHosts sets.String, 244 countToSucceed int) error { 245 framework.Logf("Waiting up to %v for satisfying expectedHosts for %v times", timeout, countToSucceed) 246 hittedHosts := sets.NewString() 247 count := 0 248 condition := func() (bool, error) { 249 result := e2enetwork.PokeHTTP(externalIP, int(httpPort), "/hostname", &e2enetwork.HTTPPokeParams{Timeout: 1 * time.Second}) 250 if result.Status != e2enetwork.HTTPSuccess { 251 return false, nil 252 } 253 254 hittedHost := strings.TrimSpace(string(result.Body)) 255 if !expectedHosts.Has(hittedHost) { 256 framework.Logf("Error hitting unexpected host: %v, reset counter: %v", hittedHost, count) 257 count = 0 258 return false, nil 259 } 260 if !hittedHosts.Has(hittedHost) { 261 hittedHosts.Insert(hittedHost) 262 framework.Logf("Missing %+v, got %+v", expectedHosts.Difference(hittedHosts), hittedHosts) 263 } 264 if hittedHosts.Equal(expectedHosts) { 265 count++ 266 if count >= countToSucceed { 267 return true, nil 268 } 269 } 270 return false, nil 271 } 272 273 if err := wait.Poll(time.Second, timeout, condition); err != nil { 274 return fmt.Errorf("error waiting for expectedHosts: %v, hittedHosts: %v, count: %v, expected count: %v", 275 expectedHosts, hittedHosts, count, countToSucceed) 276 } 277 return nil 278 }