Skip to content

Commit

Permalink
Bug 1986228: NE-310 e2e test for HSTS
Browse files Browse the repository at this point in the history
  • Loading branch information
candita committed Jul 30, 2021
1 parent e2cdf40 commit b166fd2
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 0 deletions.
3 changes: 3 additions & 0 deletions pkg/manifests/manifests.go
Expand Up @@ -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"
)
Expand Down
163 changes: 163 additions & 0 deletions 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
}
1 change: 1 addition & 0 deletions test/e2e/operator_test.go
Expand Up @@ -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()
Expand Down

0 comments on commit b166fd2

Please sign in to comment.