Skip to content

Commit 75a2b64

Browse files
authored
Issue #1579 TLSRoute Passthrough Conformance Test (normative) (#1587)
* Issue #1579 TLSRoute Passthrough Conformance Test (normative) rebase * Issue #1579 TLSRoute Passthrough - PR review update - rebase * Issue #1579 TLSRoute Passthrough - PR review update - latest
1 parent 2058ec9 commit 75a2b64

File tree

12 files changed

+428
-35
lines changed

12 files changed

+428
-35
lines changed

conformance/base/manifests.yaml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,68 @@ spec:
201201
cpu: 10m
202202
---
203203
apiVersion: v1
204+
kind: Service
205+
metadata:
206+
name: tls-backend
207+
namespace: gateway-conformance-infra
208+
spec:
209+
selector:
210+
app: tls-backend
211+
ports:
212+
- protocol: TCP
213+
port: 443
214+
targetPort: 8443
215+
---
216+
apiVersion: apps/v1
217+
kind: Deployment
218+
metadata:
219+
name: tls-backend
220+
namespace: gateway-conformance-infra
221+
labels:
222+
app: tls-backend
223+
spec:
224+
replicas: 1
225+
selector:
226+
matchLabels:
227+
app: tls-backend
228+
template:
229+
metadata:
230+
labels:
231+
app: tls-backend
232+
spec:
233+
containers:
234+
- name: tls-backend
235+
image: gcr.io/k8s-staging-ingressconformance/echoserver:v20221109-7ee2f3e
236+
volumeMounts:
237+
- name: secret-volume
238+
mountPath: /etc/secret-volume
239+
env:
240+
- name: POD_NAME
241+
valueFrom:
242+
fieldRef:
243+
fieldPath: metadata.name
244+
- name: NAMESPACE
245+
valueFrom:
246+
fieldRef:
247+
fieldPath: metadata.namespace
248+
- name: TLS_SERVER_CERT
249+
value: /etc/secret-volume/crt
250+
- name: TLS_SERVER_PRIVKEY
251+
value: /etc/secret-volume/key
252+
resources:
253+
requests:
254+
cpu: 10m
255+
volumes:
256+
- name: secret-volume
257+
secret:
258+
secretName: tls-passthrough-checks-certificate
259+
items:
260+
- key: tls.crt
261+
path: crt
262+
- key: tls.key
263+
path: key
264+
---
265+
apiVersion: v1
204266
kind: Namespace
205267
metadata:
206268
name: gateway-conformance-app-backend

conformance/conformance_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,15 @@ func TestConformance(t *testing.T) {
4444
v1alpha2.AddToScheme(client.Scheme())
4545
v1beta1.AddToScheme(client.Scheme())
4646

47-
t.Logf("Running conformance tests with %s GatewayClass", *flags.GatewayClassName)
48-
4947
supportedFeatures := parseSupportedFeatures(*flags.SupportedFeatures)
5048
exemptFeatures := parseSupportedFeatures(*flags.ExemptFeatures)
5149
for feature := range exemptFeatures {
5250
supportedFeatures[feature] = false
5351
}
5452

53+
t.Logf("Running conformance tests with %s GatewayClass\n cleanup: %t\n debug: %t\n supported features: [%v]\n exempt features: [%v]",
54+
*flags.GatewayClassName, *flags.CleanupBaseResources, *flags.ShowDebug, *flags.SupportedFeatures, *flags.ExemptFeatures)
55+
5556
cSuite := suite.New(suite.Options{
5657
Client: client,
5758
GatewayClassName: *flags.GatewayClassName,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
Copyright 2022 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 tests
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"testing"
23+
"time"
24+
25+
v1 "k8s.io/api/core/v1"
26+
"k8s.io/apimachinery/pkg/types"
27+
28+
"sigs.k8s.io/controller-runtime/pkg/client"
29+
30+
"sigs.k8s.io/gateway-api/apis/v1beta1"
31+
"sigs.k8s.io/gateway-api/conformance/utils/http"
32+
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
33+
"sigs.k8s.io/gateway-api/conformance/utils/suite"
34+
"sigs.k8s.io/gateway-api/conformance/utils/tls"
35+
)
36+
37+
func init() {
38+
ConformanceTests = append(ConformanceTests, TLSRouteSimpleSameNamespace)
39+
}
40+
41+
var TLSRouteSimpleSameNamespace = suite.ConformanceTest{
42+
ShortName: "TLSRouteSimpleSameNamespace",
43+
Description: "A single TLSRoute in the gateway-conformance-infra namespace attaches to a Gateway in the same namespace",
44+
Manifests: []string{"tests/tlsroute-simple-same-namespace.yaml"},
45+
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
46+
ns := v1beta1.Namespace("gateway-conformance-infra")
47+
routeNN := types.NamespacedName{Name: "gateway-conformance-infra-test", Namespace: string(ns)}
48+
gwNN := types.NamespacedName{Name: "gateway-tlsroute", Namespace: string(ns)}
49+
certNN := types.NamespacedName{Name: "tls-passthrough-checks-certificate", Namespace: string(ns)}
50+
51+
gwAddr, hostnames := kubernetes.GatewayAndTLSRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
52+
if len(hostnames) != 1 {
53+
t.Fatalf("unexpected error in test configuration, found %d hostnames", len(hostnames))
54+
}
55+
serverStr := string(hostnames[0])
56+
57+
cPem, keyPem, err := GetTLSSecret(suite.Client, certNN)
58+
if err != nil {
59+
t.Fatalf("unexpected error finding TLS secret: %v", err)
60+
}
61+
t.Run("Simple TLS request matching TLSRoute should reach infra-backend", func(t *testing.T) {
62+
tls.MakeTLSRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, cPem, keyPem, serverStr,
63+
http.ExpectedResponse{
64+
Request: http.Request{Host: serverStr, Path: "/"},
65+
Backend: "tls-backend",
66+
Namespace: "gateway-conformance-infra",
67+
})
68+
})
69+
},
70+
}
71+
72+
// GetTLSSecret fetches the named Secret and converts both cert and key to []byte
73+
func GetTLSSecret(client client.Client, secretName types.NamespacedName) ([]byte, []byte, error) {
74+
var cert, key []byte
75+
76+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
77+
defer cancel()
78+
79+
secret := &v1.Secret{}
80+
err := client.Get(ctx, secretName, secret)
81+
if err != nil {
82+
return cert, key, fmt.Errorf("error fetching TLS Secret: %w", err)
83+
}
84+
cert = secret.Data["tls.crt"]
85+
key = secret.Data["tls.key"]
86+
87+
return cert, key, nil
88+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
apiVersion: gateway.networking.k8s.io/v1alpha2
2+
kind: TLSRoute
3+
metadata:
4+
name: gateway-conformance-infra-test
5+
namespace: gateway-conformance-infra
6+
spec:
7+
parentRefs:
8+
- name: gateway-tlsroute
9+
namespace: gateway-conformance-infra
10+
hostnames:
11+
- abc.example.com
12+
rules:
13+
- backendRefs:
14+
- name: tls-backend
15+
port: 443
16+
---
17+
apiVersion: gateway.networking.k8s.io/v1beta1
18+
kind: Gateway
19+
metadata:
20+
name: gateway-tlsroute
21+
namespace: gateway-conformance-infra
22+
spec:
23+
gatewayClassName: "{GATEWAY_CLASS_NAME}"
24+
listeners:
25+
- name: https
26+
port: 443
27+
protocol: TLS
28+
hostname: "*.example.com"
29+
allowedRoutes:
30+
namespaces:
31+
from: Same
32+
kinds:
33+
- kind: TLSRoute
34+
tls:
35+
mode: Passthrough

conformance/utils/config/timeout.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ type TimeoutConfig struct {
5151
// Max value for conformant implementation: None
5252
HTTPRouteMustHaveCondition time.Duration
5353

54-
// HTTPRouteMustHaveParents represents the maximum time for an HTTPRoute to have parents in status that match the expected parents.
54+
// RouteMustHaveParents represents the maximum time for an xRoute to have parents in status that match the expected parents.
5555
// Max value for conformant implementation: None
56-
HTTPRouteMustHaveParents time.Duration
56+
RouteMustHaveParents time.Duration
5757

5858
// ManifestFetchTimeout represents the maximum time for getting content from a https:// URL.
5959
// Max value for conformant implementation: None
@@ -70,6 +70,11 @@ type TimeoutConfig struct {
7070
// RequestTimeout represents the maximum time for making an HTTP Request with the roundtripper.
7171
// Max value for conformant implementation: None
7272
RequestTimeout time.Duration
73+
74+
// RequiredConsecutiveSuccesses is the number of requests that must succeed in a row
75+
// to consider a response "consistent" before making additional assertions on the response body.
76+
// If this number is not reached within MaxTimeToConsistency, the test will fail.
77+
RequiredConsecutiveSuccesses int
7378
}
7479

7580
// DefaultTimeoutConfig populates a TimeoutConfig with the default values.
@@ -83,11 +88,12 @@ func DefaultTimeoutConfig() TimeoutConfig {
8388
GWCMustBeAccepted: 180 * time.Second,
8489
HTTPRouteMustNotHaveParents: 60 * time.Second,
8590
HTTPRouteMustHaveCondition: 60 * time.Second,
86-
HTTPRouteMustHaveParents: 60 * time.Second,
91+
RouteMustHaveParents: 60 * time.Second,
8792
ManifestFetchTimeout: 10 * time.Second,
8893
MaxTimeToConsistency: 30 * time.Second,
8994
NamespacesMustBeReady: 300 * time.Second,
9095
RequestTimeout: 10 * time.Second,
96+
RequiredConsecutiveSuccesses: 3,
9197
}
9298
}
9399

@@ -117,8 +123,8 @@ func SetupTimeoutConfig(timeoutConfig *TimeoutConfig) {
117123
if timeoutConfig.HTTPRouteMustHaveCondition == 0 {
118124
timeoutConfig.HTTPRouteMustHaveCondition = defaultTimeoutConfig.HTTPRouteMustHaveCondition
119125
}
120-
if timeoutConfig.HTTPRouteMustHaveParents == 0 {
121-
timeoutConfig.HTTPRouteMustHaveParents = defaultTimeoutConfig.HTTPRouteMustHaveParents
126+
if timeoutConfig.RouteMustHaveParents == 0 {
127+
timeoutConfig.RouteMustHaveParents = defaultTimeoutConfig.RouteMustHaveParents
122128
}
123129
if timeoutConfig.ManifestFetchTimeout == 0 {
124130
timeoutConfig.ManifestFetchTimeout = defaultTimeoutConfig.ManifestFetchTimeout

conformance/utils/http/http.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,6 @@ type Response struct {
8282
AbsentHeaders []string
8383
}
8484

85-
// requiredConsecutiveSuccesses is the number of requests that must succeed in a row
86-
// for MakeRequestAndExpectEventuallyConsistentResponse to consider the response "consistent"
87-
// before making additional assertions on the response body. If this number is not reached within
88-
// maxTimeToConsistency, the test will fail.
89-
const requiredConsecutiveSuccesses = 3
90-
9185
// MakeRequestAndExpectEventuallyConsistentResponse makes a request with the given parameters,
9286
// understanding that the request may fail for some amount of time.
9387
//
@@ -96,6 +90,14 @@ const requiredConsecutiveSuccesses = 3
9690
func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripper.RoundTripper, timeoutConfig config.TimeoutConfig, gwAddr string, expected ExpectedResponse) {
9791
t.Helper()
9892

93+
req := MakeRequest(t, &expected, gwAddr, "HTTP", "http")
94+
95+
WaitForConsistentResponse(t, r, req, expected, timeoutConfig.RequiredConsecutiveSuccesses, timeoutConfig.MaxTimeToConsistency)
96+
}
97+
98+
func MakeRequest(t *testing.T, expected *ExpectedResponse, gwAddr, protocol, scheme string) roundtripper.Request {
99+
t.Helper()
100+
99101
if expected.Request.Method == "" {
100102
expected.Request.Method = "GET"
101103
}
@@ -104,15 +106,15 @@ func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripp
104106
expected.Response.StatusCode = 200
105107
}
106108

107-
t.Logf("Making %s request to http://%s%s", expected.Request.Method, gwAddr, expected.Request.Path)
109+
t.Logf("Making %s request to %s://%s%s", expected.Request.Method, scheme, gwAddr, expected.Request.Path)
108110

109111
path, query, _ := strings.Cut(expected.Request.Path, "?")
110112

111113
req := roundtripper.Request{
112114
Method: expected.Request.Method,
113115
Host: expected.Request.Host,
114-
URL: url.URL{Scheme: "http", Host: gwAddr, Path: path, RawQuery: query},
115-
Protocol: "HTTP",
116+
URL: url.URL{Scheme: scheme, Host: gwAddr, Path: path, RawQuery: query},
117+
Protocol: protocol,
116118
Headers: map[string][]string{},
117119
UnfollowRedirect: expected.Request.UnfollowRedirect,
118120
}
@@ -129,12 +131,12 @@ func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripp
129131
}
130132
req.Headers["X-Echo-Set-Header"] = []string{strings.Join(backendSetHeaders, ",")}
131133

132-
WaitForConsistentResponse(t, r, req, expected, requiredConsecutiveSuccesses, timeoutConfig.MaxTimeToConsistency)
134+
return req
133135
}
134136

135-
// awaitConvergence runs the given function until it returns 'true' `threshold` times in a row.
137+
// AwaitConvergence runs the given function until it returns 'true' `threshold` times in a row.
136138
// Each failed attempt has a 1s delay; successful attempts have no delay.
137-
func awaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Duration, fn func(elapsed time.Duration) bool) {
139+
func AwaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Duration, fn func(elapsed time.Duration) bool) {
138140
successes := 0
139141
attempts := 0
140142
start := time.Now()
@@ -162,7 +164,7 @@ func awaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Dur
162164
select {
163165
// Capture the overall timeout
164166
case <-to:
165-
t.Fatalf("timeout while waiting after %d attempts, %d/%d sucessess", attempts, successes, threshold)
167+
t.Fatalf("timeout while waiting after %d attempts, %d/%d successes", attempts, successes, threshold)
166168
// And the per-try delay
167169
case <-time.After(delay):
168170
}
@@ -173,7 +175,7 @@ func awaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Dur
173175
// the expected response consistently. The provided threshold determines how many times in
174176
// a row this must occur to be considered "consistent".
175177
func WaitForConsistentResponse(t *testing.T, r roundtripper.RoundTripper, req roundtripper.Request, expected ExpectedResponse, threshold int, maxTimeToConsistency time.Duration) {
176-
awaitConvergence(t, threshold, maxTimeToConsistency, func(elapsed time.Duration) bool {
178+
AwaitConvergence(t, threshold, maxTimeToConsistency, func(elapsed time.Duration) bool {
177179
cReq, cRes, err := r.CaptureRoundTrip(req)
178180
if err != nil {
179181
t.Logf("Request failed, not ready yet: %v (after %v)", err.Error(), elapsed)
@@ -312,7 +314,7 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques
312314
return nil
313315
}
314316

315-
// Get User-defined test case name or generate from expected response to a given request.
317+
// GetTestCaseName gets the user-defined test case name or generates one from expected response to a given request.
316318
func (er *ExpectedResponse) GetTestCaseName(i int) string {
317319

318320
// If TestCase name is provided then use that or else generate one.

conformance/utils/kubernetes/certificate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func MustCreateSelfSignedCertSecret(t *testing.T, namespace, secretName string,
7171
return newSecret
7272
}
7373

74-
// generateRSACert generates a basic self signed certificate valir for a year
74+
// generateRSACert generates a basic self signed certificate valid for a year
7575
func generateRSACert(host string, keyOut, certOut io.Writer) error {
7676
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
7777
if err != nil {

0 commit comments

Comments
 (0)