From 3a12febcbfae560b9bb2bd8e29e42ba99821c21a Mon Sep 17 00:00:00 2001 From: Maartje Eyskens Date: Mon, 29 May 2023 14:18:26 +0000 Subject: [PATCH] Add TLSRoute + ReferenceGrant check This checks ReferenceGrant rules for TLSRoute against accepting an invalid ReferenceGrant for the backend service. Signed-off-by: Maartje Eyskens --- conformance/base/manifests.yaml | 62 ++++++++ .../httproute-invalid-reference-grant.go | 3 +- .../tests/tlsroute-invalid-reference-grant.go | 59 ++++++++ .../tlsroute-invalid-reference-grant.yaml | 141 ++++++++++++++++++ conformance/utils/config/timeout.go | 8 + conformance/utils/kubernetes/helpers.go | 35 ++++- conformance/utils/suite/experimental_suite.go | 2 + conformance/utils/suite/suite.go | 2 + 8 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 conformance/tests/tlsroute-invalid-reference-grant.go create mode 100644 conformance/tests/tlsroute-invalid-reference-grant.yaml diff --git a/conformance/base/manifests.yaml b/conformance/base/manifests.yaml index b354ceff88..7087aec109 100644 --- a/conformance/base/manifests.yaml +++ b/conformance/base/manifests.yaml @@ -307,6 +307,68 @@ metadata: --- apiVersion: v1 kind: Service +metadata: + name: tls-backend + namespace: gateway-conformance-app-backend +spec: + selector: + app: tls-backend + ports: + - protocol: TCP + port: 443 + targetPort: 8443 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tls-backend + namespace: gateway-conformance-app-backend + labels: + app: tls-backend +spec: + replicas: 1 + selector: + matchLabels: + app: tls-backend + template: + metadata: + labels: + app: tls-backend + spec: + containers: + - name: tls-backend + image: gcr.io/k8s-staging-ingressconformance/echoserver:v20221109-7ee2f3e + volumeMounts: + - name: secret-volume + mountPath: /etc/secret-volume + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: TLS_SERVER_CERT + value: /etc/secret-volume/crt + - name: TLS_SERVER_PRIVKEY + value: /etc/secret-volume/key + resources: + requests: + cpu: 10m + volumes: + - name: secret-volume + secret: + secretName: tls-passthrough-checks-certificate + items: + - key: tls.crt + path: crt + - key: tls.key + path: key +--- +apiVersion: v1 +kind: Service metadata: name: app-backend-v1 namespace: gateway-conformance-app-backend diff --git a/conformance/tests/httproute-invalid-reference-grant.go b/conformance/tests/httproute-invalid-reference-grant.go index 3e5bf8516f..e209f0f24a 100644 --- a/conformance/tests/httproute-invalid-reference-grant.go +++ b/conformance/tests/httproute-invalid-reference-grant.go @@ -1,5 +1,5 @@ /* -Copyright 2022 The Kubernetes Authors. +Copyright 2023 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -47,7 +47,6 @@ var HTTPRouteInvalidReferenceGrant = suite.ConformanceTest{ gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) t.Run("HTTPRoute with BackendRef in another namespace and no ReferenceGrant covering the Service has a ResolvedRefs Condition with status False and Reason RefNotPermitted", func(t *testing.T) { - resolvedRefsCond := metav1.Condition{ Type: string(v1beta1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, diff --git a/conformance/tests/tlsroute-invalid-reference-grant.go b/conformance/tests/tlsroute-invalid-reference-grant.go new file mode 100644 index 0000000000..7b4ebc5cd2 --- /dev/null +++ b/conformance/tests/tlsroute-invalid-reference-grant.go @@ -0,0 +1,59 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tests + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "sigs.k8s.io/gateway-api/apis/v1beta1" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" +) + +func init() { + ConformanceTests = append(ConformanceTests, TLSRouteInvalidReferenceGrant) +} + +var TLSRouteInvalidReferenceGrant = suite.ConformanceTest{ + ShortName: "TLSRouteInvalidReferenceGrant", + Description: "A single TLSRoute in the gateway-conformance-infra namespace, with a backendRef in another namespace without valid ReferenceGrant, should have the ResolvedRefs condition set to False", + Features: []suite.SupportedFeature{ + suite.SupportGateway, + suite.SupportTLSRoute, + suite.SupportReferenceGrant, + }, + Manifests: []string{"tests/tlsroute-invalid-reference-grant.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + routeNN := types.NamespacedName{Name: "gateway-conformance-infra-test", Namespace: "gateway-conformance-infra"} + gwNN := types.NamespacedName{Name: "gateway-tlsroute", Namespace: "gateway-conformance-infra"} + + kubernetes.GatewayAndTLSRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + t.Run("TLSRoute with BackendRef in another namespace and no ReferenceGrant covering the Service has a ResolvedRefs Condition with status False and Reason RefNotPermitted", func(t *testing.T) { + resolvedRefsCond := metav1.Condition{ + Type: string(v1beta1.RouteConditionResolvedRefs), + Status: metav1.ConditionFalse, + Reason: string(v1beta1.RouteReasonRefNotPermitted), + } + + kubernetes.TLSRouteMustHaveCondition(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN, resolvedRefsCond) + }) + }, +} diff --git a/conformance/tests/tlsroute-invalid-reference-grant.yaml b/conformance/tests/tlsroute-invalid-reference-grant.yaml new file mode 100644 index 0000000000..85973012bc --- /dev/null +++ b/conformance/tests/tlsroute-invalid-reference-grant.yaml @@ -0,0 +1,141 @@ +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: reference-grant-wrong-namespace + namespace: gateway-conformance-infra +spec: + from: + - group: gateway.networking.k8s.io + kind: TLSRoute + namespace: gateway-conformance-infra + to: + - group: "" + kind: Service + name: tls-backend +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: reference-grant-wrong-from-group + namespace: gateway-conformance-app-backend +spec: + from: + - group: not-the-group-youre-looking-for + kind: TLSRoute + namespace: gateway-conformance-infra + to: + - group: "" + kind: Service + name: tls-backend +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: reference-grant-wrong-from-kind + namespace: gateway-conformance-app-backend +spec: + from: + - group: gateway.networking.k8s.io + kind: Gateway + namespace: gateway-conformance-infra + to: + - group: "" + kind: Service + name: tls-backend +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: reference-grant-wrong-from-namespace + namespace: gateway-conformance-app-backend +spec: + from: + - group: gateway.networking.k8s.io + kind: TLSRoute + namespace: not-the-namespace-youre-looking-for + to: + - group: "" + kind: Service + name: tls-backend +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: reference-grant-wrong-to-group + namespace: gateway-conformance-app-backend +spec: + from: + - group: gateway.networking.k8s.io + kind: TLSRoute + namespace: gateway-conformance-infra + to: + - group: not-the-group-youre-looking-for + kind: Service + name: tls-backend +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: reference-grant-wrong-to-kind + namespace: gateway-conformance-app-backend +spec: + from: + - group: gateway.networking.k8s.io + kind: TLSRoute + namespace: gateway-conformance-infra + to: + - group: "" + kind: Secret + name: tls-backend +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: reference-grant-wrong-to-name + namespace: gateway-conformance-app-backend +spec: + from: + - group: gateway.networking.k8s.io + kind: TLSRoute + namespace: gateway-conformance-infra + to: + - group: "" + kind: Service + name: not-the-service-youre-looking-for +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TLSRoute +metadata: + name: gateway-conformance-infra-test + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: gateway-tlsroute + hostnames: + - abc.example.com + rules: + - backendRefs: + - name: tls-backend + namespace: gateway-conformance-app-backend + port: 443 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: gateway-tlsroute + namespace: gateway-conformance-infra +spec: + gatewayClassName: "{GATEWAY_CLASS_NAME}" + listeners: + - name: https + port: 443 + protocol: TLS + hostname: "*.example.com" + allowedRoutes: + namespaces: + from: Same + kinds: + - kind: TLSRoute + tls: + mode: Passthrough diff --git a/conformance/utils/config/timeout.go b/conformance/utils/config/timeout.go index b72cf7f938..dbaf2bec0f 100644 --- a/conformance/utils/config/timeout.go +++ b/conformance/utils/config/timeout.go @@ -51,6 +51,10 @@ type TimeoutConfig struct { // Max value for conformant implementation: None HTTPRouteMustHaveCondition time.Duration + // TLSRouteMustHaveCondition represents the maximum time for an TLSRoute to have the supplied Condition. + // Max value for conformant implementation: None + TLSRouteMustHaveCondition time.Duration + // RouteMustHaveParents represents the maximum time for an xRoute to have parents in status that match the expected parents. // Max value for conformant implementation: None RouteMustHaveParents time.Duration @@ -95,6 +99,7 @@ func DefaultTimeoutConfig() TimeoutConfig { GWCMustBeAccepted: 180 * time.Second, HTTPRouteMustNotHaveParents: 60 * time.Second, HTTPRouteMustHaveCondition: 60 * time.Second, + TLSRouteMustHaveCondition: 60 * time.Second, RouteMustHaveParents: 60 * time.Second, ManifestFetchTimeout: 10 * time.Second, MaxTimeToConsistency: 30 * time.Second, @@ -149,4 +154,7 @@ func SetupTimeoutConfig(timeoutConfig *TimeoutConfig) { if timeoutConfig.LatestObservedGenerationSet == 0 { timeoutConfig.LatestObservedGenerationSet = defaultTimeoutConfig.LatestObservedGenerationSet } + if timeoutConfig.TLSRouteMustHaveCondition == 0 { + timeoutConfig.TLSRouteMustHaveCondition = defaultTimeoutConfig.TLSRouteMustHaveCondition + } } diff --git a/conformance/utils/kubernetes/helpers.go b/conformance/utils/kubernetes/helpers.go index dbf5e2ff1a..ffdc8b7802 100644 --- a/conformance/utils/kubernetes/helpers.go +++ b/conformance/utils/kubernetes/helpers.go @@ -355,7 +355,6 @@ func HTTPRouteMustHaveNoAcceptedParents(t *testing.T, client client.Client, time var actual []v1beta1.RouteParentStatus emptyChecked := false waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.HTTPRouteMustNotHaveParents, true, func(ctx context.Context) (bool, error) { - route := &v1beta1.HTTPRoute{} err := client.Get(ctx, routeName, route) if err != nil { @@ -528,7 +527,6 @@ func HTTPRouteMustHaveCondition(t *testing.T, client client.Client, timeoutConfi var conditionFound bool for _, parent := range parents { if err := ConditionsHaveLatestObservedGeneration(route, parent.Conditions); err != nil { - t.Logf("HTTPRoute(parentRef=%v) %v", parentRefToString(parent.ParentRef), err) return false, nil } @@ -611,6 +609,39 @@ func GatewayAndTLSRoutesMustBeAccepted(t *testing.T, c client.Client, timeoutCon return gwAddr, hostnames } +// TLSRouteMustHaveCondition checks that the supplied TLSRoute has the supplied Condition, +// halting after the specified timeout is exceeded. +func TLSRouteMustHaveCondition(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, routeNN types.NamespacedName, gwNN types.NamespacedName, condition metav1.Condition) { + t.Helper() + + waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.TLSRouteMustHaveCondition, true, func(ctx context.Context) (bool, error) { + route := &v1alpha2.TLSRoute{} + err := client.Get(ctx, routeNN, route) + if err != nil { + return false, fmt.Errorf("error fetching TLSRoute: %w", err) + } + + parents := route.Status.Parents + var conditionFound bool + for _, parent := range parents { + if err := ConditionsHaveLatestObservedGeneration(route, parent.Conditions); err != nil { + t.Logf("TLSRoute(parentRef=%v) %v", parentRefToString(parent.ParentRef), err) + return false, nil + } + + if parent.ParentRef.Name == v1beta1.ObjectName(gwNN.Name) && (parent.ParentRef.Namespace == nil || string(*parent.ParentRef.Namespace) == gwNN.Namespace) { + if findConditionInList(t, parent.Conditions, condition.Type, string(condition.Status), condition.Reason) { + conditionFound = true + } + } + } + + return conditionFound, nil + }) + + require.NoErrorf(t, waitErr, "error waiting for TLSRoute status to have a Condition matching expectations") +} + // TODO(mikemorris): this and parentsMatch could possibly be rewritten as a generic function? func listenersMatch(t *testing.T, expected, actual []v1beta1.ListenerStatus) bool { t.Helper() diff --git a/conformance/utils/suite/experimental_suite.go b/conformance/utils/suite/experimental_suite.go index 1d97c02db3..eed575e1a3 100644 --- a/conformance/utils/suite/experimental_suite.go +++ b/conformance/utils/suite/experimental_suite.go @@ -189,6 +189,8 @@ func (suite *ExperimentalConformanceTestSuite) Setup(t *testing.T) { suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) secret = kubernetes.MustCreateSelfSignedCertSecret(t, "gateway-conformance-infra", "tls-passthrough-checks-certificate", []string{"abc.example.com"}) suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) + secret = kubernetes.MustCreateSelfSignedCertSecret(t, "gateway-conformance-app-backend", "tls-passthrough-checks-certificate", []string{"abc.example.com"}) + suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) t.Logf("Test Setup: Ensuring Gateways and Pods from base manifests are ready") namespaces := []string{ diff --git a/conformance/utils/suite/suite.go b/conformance/utils/suite/suite.go index 75d78c9309..71452c65d7 100644 --- a/conformance/utils/suite/suite.go +++ b/conformance/utils/suite/suite.go @@ -159,6 +159,8 @@ func (suite *ConformanceTestSuite) Setup(t *testing.T) { suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) secret = kubernetes.MustCreateSelfSignedCertSecret(t, "gateway-conformance-infra", "tls-passthrough-checks-certificate", []string{"abc.example.com"}) suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) + secret = kubernetes.MustCreateSelfSignedCertSecret(t, "gateway-conformance-app-backend", "tls-passthrough-checks-certificate", []string{"abc.example.com"}) + suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup) t.Logf("Test Setup: Ensuring Gateways and Pods from base manifests are ready") namespaces := []string{