From b166fd20eebc229512f8f722eb56006b5ba89a92 Mon Sep 17 00:00:00 2001 From: candita Date: Tue, 27 Jul 2021 23:43:12 -0400 Subject: [PATCH] Bug 1986228: NE-310 e2e test for HSTS --- pkg/manifests/manifests.go | 3 + test/e2e/hsts_policy_test.go | 163 +++++++++++++++++++++++++++++++++++ test/e2e/operator_test.go | 1 + 3 files changed, 167 insertions(+) create mode 100644 test/e2e/hsts_policy_test.go diff --git a/pkg/manifests/manifests.go b/pkg/manifests/manifests.go index 195bc16370..f7fe2db289 100644 --- a/pkg/manifests/manifests.go +++ b/pkg/manifests/manifests.go @@ -69,6 +69,9 @@ const ( // instance. DefaultIngressControllerName = "default" + // ClusterIngressConfigName is the name of the cluster Ingress Config + ClusterIngressConfigName = "cluster" + NamespaceManifest = "manifests/00-namespace.yaml" CustomResourceDefinitionManifest = "manifests/00-custom-resource-definition.yaml" ) diff --git a/test/e2e/hsts_policy_test.go b/test/e2e/hsts_policy_test.go new file mode 100644 index 0000000000..0dd91f9870 --- /dev/null +++ b/test/e2e/hsts_policy_test.go @@ -0,0 +1,163 @@ +// +build e2e + +package e2e + +import ( + "context" + "testing" + "time" + + configv1 "github.com/openshift/api/config/v1" + operatorv1 "github.com/openshift/api/operator/v1" + routev1 "github.com/openshift/api/route/v1" + "github.com/openshift/cluster-ingress-operator/pkg/operator/controller" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" +) + +// Helper function for int32 pointers +func intPtr(s int32) *int32 { + return &s +} + +func TestHstsPolicyWorks(t *testing.T) { + icName := types.NamespacedName{Namespace: operatorNamespace, Name: "hsts-policy"} + domain := icName.Name + "." + dnsConfig.Spec.BaseDomain + domain2 := icName.Name + "2." + dnsConfig.Spec.BaseDomain + + // Setup a Required HSTS Policy for the ingress config + maxAgePolicy := configv1.MaxAgePolicy{LargestMaxAge: intPtr(99999), SmallestMaxAge: intPtr(1)} + domainPatterns := []string{domain, domain2} + hstsPolicy := configv1.RequiredHSTSPolicy{ + DomainPatterns: domainPatterns, // this policy will only validate routes with hosts in the DomainPatterns + PreloadPolicy: configv1.RequirePreloadPolicy, + IncludeSubDomainsPolicy: configv1.RequireIncludeSubDomains, + MaxAge: maxAgePolicy, + } + + ing := &configv1.Ingress{} + + // Update the ingress config with the new HSTS policy + if err := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (bool, error) { + // Get the ingress config + if err := kclient.Get(context.TODO(), clusterName, ing); err != nil { + t.Logf("Get ingress config failed: %v, retrying...", err) + return false, nil + } + if ing.Spec.RequiredHSTSPolicies == nil { + ing.Spec.RequiredHSTSPolicies = []configv1.RequiredHSTSPolicy{} + } + ing.Spec.RequiredHSTSPolicies = append(ing.Spec.RequiredHSTSPolicies, hstsPolicy) + // Update the ingress config + if err := kclient.Update(context.TODO(), ing); err != nil { + t.Logf("failed to update ingress config: %v", err) + return false, nil + } + return true, nil + }); err != nil { + t.Fatalf("failed to update ingress controller: %v", err) + } + + p := ing.Spec.RequiredHSTSPolicies[0] + t.Logf("created a RequiredHSTSPolicy with DomainPatterns: %v, preload policy: %s, includeSubDomains policy: %s, largest age: %d, smallest age: %d", p.DomainPatterns, p.PreloadPolicy, p.IncludeSubDomainsPolicy, *p.MaxAge.LargestMaxAge, *p.MaxAge.SmallestMaxAge) + + conditions := []operatorv1.OperatorCondition{ + {Type: operatorv1.IngressControllerAvailableConditionType, Status: operatorv1.ConditionTrue}, + {Type: operatorv1.LoadBalancerManagedIngressConditionType, Status: operatorv1.ConditionFalse}, + {Type: operatorv1.DNSManagedIngressConditionType, Status: operatorv1.ConditionFalse}, + } + + // Create a new ingress controller, so we don't disrupt the test system's ingress controller + ic := newPrivateController(icName, domain) + if err := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (bool, error) { + if err := kclient.Create(context.TODO(), ic); err != nil { + t.Logf("failed to create ingresscontroller %s: %v, retrying...", icName, err) + return false, nil + } + return true, nil + }); err != nil { + t.Fatalf("failed to create ingress controller: %v", err) + } + defer assertIngressControllerDeleted(t, kclient, ic) + // Wait for it to come up to speed + if err := waitForIngressControllerCondition(t, kclient, 5*time.Minute, icName, conditions...); err != nil { + t.Fatalf("failed to observe expected conditions: %v", err) + } + + // Get router deployment info + deployment := &appsv1.Deployment{} + if err := kclient.Get(context.TODO(), controller.RouterDeploymentName(ic), deployment); err != nil { + t.Fatalf("failed to get ingresscontroller deployment: %v", err) + } + + // Get ingress service info + service := &corev1.Service{} + if err := kclient.Get(context.TODO(), controller.InternalIngressControllerServiceName(ic), service); err != nil { + t.Fatalf("failed to get ingresscontroller service: %v", err) + } + + // Create some pods in the deployment namespace + echoPod := buildEchoPod("hsts-policy-echo", deployment.Namespace) + if err := kclient.Create(context.TODO(), echoPod); err != nil { + t.Fatalf("failed to create pod %s/%s: %v", echoPod.Namespace, echoPod.Name, err) + } + defer func() { + if err := kclient.Delete(context.TODO(), echoPod); err != nil { + t.Fatalf("failed to delete pod %s/%s: %v", echoPod.Namespace, echoPod.Name, err) + } + }() + + echoService := buildEchoService(echoPod.Name, echoPod.Namespace, echoPod.ObjectMeta.Labels) + if err := kclient.Create(context.TODO(), echoService); err != nil { + t.Fatalf("failed to create service %s/%s: %v", echoService.Namespace, echoService.Name, err) + } + defer func() { + if err := kclient.Delete(context.TODO(), echoService); err != nil { + t.Fatalf("failed to delete service %s/%s: %v", echoService.Namespace, echoService.Name, err) + } + }() + + // Create a route that should pass the HSTS policy validation + t.Logf("Creating first route at %v", time.Now()) + echoRoute := buildRouteWithHSTS(echoPod.Name, echoPod.Namespace, echoService.Name, domain, "max-age=99999;preload;includesubdomains") + + if err := kclient.Create(context.TODO(), echoRoute); err != nil { + t.Fatalf("failed to create route %s/%s: %v", echoRoute.Namespace, echoRoute.Name, err) + } else { + t.Logf("created a route at %v: %s/%s with annotation %s", time.Now(), echoRoute.Namespace, echoRoute.Name, echoRoute.Annotations) + } + + // Create a route that should fail the HSTS policy validation + t.Logf("Creating second route at %v", time.Now()) + echoRoute2 := buildRouteWithHSTS(echoPod.Name+"2", echoPod.Namespace, echoService.Name, domain2, "max-age=99999999") + + if err := kclient.Create(context.TODO(), echoRoute2); err == nil { + t.Fatalf("failed to reject invalid route %s/%s, max-age 99999999", echoRoute2.Namespace, echoRoute2.Name) + } else { + t.Logf("rejected an invalid route at %v: %s/%s with annotation %s: %v", time.Now(), echoRoute2.Namespace, echoRoute2.Name, echoRoute2.Annotations, err) + } + + defer func() { + if err := kclient.Delete(context.TODO(), echoRoute); err != nil { + t.Fatalf("failed to delete route %s/%s: %v", echoRoute.Namespace, echoRoute.Name, err) + } + // okay if this fails, it shouldn't have been created anyway + kclient.Delete(context.TODO(), echoRoute2) + }() +} + +func buildRouteWithHSTS(podName, namespace, serviceName, domain, annotation string) *routev1.Route { + route := buildRoute(podName, namespace, serviceName) + route.Spec.Host = domain + route.Spec.TLS = &routev1.TLSConfig{Termination: routev1.TLSTerminationReencrypt} + if route.Annotations == nil { + route.Annotations = map[string]string{} + } + route.Annotations["haproxy.router.openshift.io/hsts_header"] = annotation + + return route +} diff --git a/test/e2e/operator_test.go b/test/e2e/operator_test.go index 21cd3c5b22..79f9226be1 100644 --- a/test/e2e/operator_test.go +++ b/test/e2e/operator_test.go @@ -89,6 +89,7 @@ var dnsConfig configv1.DNS var infraConfig configv1.Infrastructure var operatorNamespace = operatorcontroller.DefaultOperatorNamespace var defaultName = types.NamespacedName{Namespace: operatorNamespace, Name: manifests.DefaultIngressControllerName} +var clusterName = types.NamespacedName{Namespace: operatorNamespace, Name: manifests.ClusterIngressConfigName} func TestMain(m *testing.M) { kubeConfig, err := config.GetConfig()