Skip to content

Commit bee090d

Browse files
authored
Invalid BackendTLSPolicy conformance test (#3930)
1 parent 78496d8 commit bee090d

File tree

6 files changed

+225
-26
lines changed

6 files changed

+225
-26
lines changed

conformance/tests/backendtlspolicy.go

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 The Kubernetes Authors.
2+
Copyright 2025 The Kubernetes Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -19,9 +19,12 @@ package tests
1919
import (
2020
"testing"
2121

22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
2224
"k8s.io/apimachinery/pkg/types"
2325

2426
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
27+
"sigs.k8s.io/gateway-api/apis/v1alpha2"
2528
h "sigs.k8s.io/gateway-api/conformance/utils/http"
2629
"sigs.k8s.io/gateway-api/conformance/utils/kubernetes"
2730
"sigs.k8s.io/gateway-api/conformance/utils/suite"
@@ -35,7 +38,7 @@ func init() {
3538

3639
var BackendTLSPolicy = suite.ConformanceTest{
3740
ShortName: "BackendTLSPolicy",
38-
Description: "A single service that is targeted by a BackendTLSPolicy must successfully complete TLS termination",
41+
Description: "BackendTLSPolicy must be used to configure TLS connection between gateway and backend",
3942
Features: []features.FeatureName{
4043
features.SupportGateway,
4144
features.SupportHTTPRoute,
@@ -51,10 +54,25 @@ var BackendTLSPolicy = suite.ConformanceTest{
5154
gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gatewayv1.HTTPRoute{}, false, routeNN)
5255
kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN)
5356

57+
policyCond := metav1.Condition{
58+
Type: string(v1alpha2.PolicyConditionAccepted),
59+
Status: metav1.ConditionTrue,
60+
Reason: string(v1alpha2.PolicyReasonAccepted),
61+
}
62+
63+
validPolicyNN := types.NamespacedName{Name: "normative-test-backendtlspolicy", Namespace: ns}
64+
kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, validPolicyNN, gwNN, policyCond)
65+
66+
invalidPolicyNN := types.NamespacedName{Name: "backendtlspolicy-host-mismatch", Namespace: ns}
67+
kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, invalidPolicyNN, gwNN, policyCond)
68+
69+
invalidCertPolicyNN := types.NamespacedName{Name: "backendtlspolicy-cert-mismatch", Namespace: ns}
70+
kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, invalidCertPolicyNN, gwNN, policyCond)
71+
5472
serverStr := "abc.example.com"
5573

56-
// Verify that the response to a backend-tls-only call to /backendTLS will return the matching SNI.
57-
t.Run("Simple HTTP request targeting BackendTLSPolicy should reach infra-backend", func(t *testing.T) {
74+
// Verify that the request sent to Service with valid BackendTLSPolicy should succeed.
75+
t.Run("HTTP request sent to Service with valid BackendTLSPolicy should succeed", func(t *testing.T) {
5876
h.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr,
5977
h.ExpectedResponse{
6078
Namespace: ns,
@@ -73,8 +91,8 @@ var BackendTLSPolicy = suite.ConformanceTest{
7391
if err != nil {
7492
t.Fatalf("unexpected error finding TLS secret: %v", err)
7593
}
76-
// Verify that the response to a re-encrypted call to /backendTLS will return the matching SNI.
77-
t.Run("Re-encrypt HTTPS request targeting BackendTLSPolicy should reach infra-backend", func(t *testing.T) {
94+
// Verify that the request to a re-encrypted call to /backendTLS should succeed.
95+
t.Run("Re-encrypt HTTPS request sent to Service with valid BackendTLSPolicy should succeed", func(t *testing.T) {
7896
tls.MakeTLSRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, cPem, keyPem, serverStr,
7997
h.ExpectedResponse{
8098
Namespace: ns,
@@ -86,5 +104,31 @@ var BackendTLSPolicy = suite.ConformanceTest{
86104
Response: h.Response{StatusCode: 200},
87105
})
88106
})
107+
108+
// Verify that the request sent to a Service targeted by a BackendTLSPolicy with mismatched host will fail.
109+
t.Run("HTTP request sent to Service targeted by BackendTLSPolicy with mismatched hostname should return an HTTP error", func(t *testing.T) {
110+
h.MakeRequestAndExpectFailure(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr,
111+
h.ExpectedResponse{
112+
Namespace: ns,
113+
Request: h.Request{
114+
Host: serverStr,
115+
Path: "/backendTLSHostMismatch",
116+
SNI: serverStr,
117+
},
118+
})
119+
})
120+
121+
// Verify that request sent to Service targeted by BackendTLSPolicy with mismatched cert should failed.
122+
t.Run("HTTP request send to Service targeted by BackendTLSPolicy with mismatched cert should return HTTP error", func(t *testing.T) {
123+
h.MakeRequestAndExpectFailure(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr,
124+
h.ExpectedResponse{
125+
Namespace: ns,
126+
Request: h.Request{
127+
Host: serverStr,
128+
Path: "/backendTLSCertMismatch",
129+
SNI: serverStr,
130+
},
131+
})
132+
})
89133
},
90134
}

conformance/tests/backendtlspolicy.yaml

Lines changed: 105 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,6 @@ spec:
3131
kinds:
3232
- kind: HTTPRoute
3333
---
34-
apiVersion: gateway.networking.k8s.io/v1alpha3
35-
kind: BackendTLSPolicy
36-
metadata:
37-
name: normative-test-backendtlspolicy
38-
namespace: gateway-conformance-infra
39-
spec:
40-
targetRefs:
41-
- group: ""
42-
kind: Service
43-
name: "backendtlspolicy-test"
44-
sectionName: "btls"
45-
validation:
46-
caCertificateRefs:
47-
- group: ""
48-
kind: ConfigMap
49-
# This secret is generated dynamically by the test suite.
50-
name: "backend-tls-checks-certificate"
51-
hostname: "abc.example.com"
52-
---
5334
apiVersion: gateway.networking.k8s.io/v1
5435
kind: HTTPRoute
5536
metadata:
@@ -71,6 +52,24 @@ spec:
7152
- path:
7253
type: Exact
7354
value: /backendTLS
55+
- backendRefs:
56+
- group: ""
57+
kind: Service
58+
name: backendtlspolicy-host-mismatch-test
59+
port: 443
60+
matches:
61+
- path:
62+
type: Exact
63+
value: /backendTLSHostMismatch
64+
- backendRefs:
65+
- group: ""
66+
kind: Service
67+
name: backendtlspolicy-cert-mismatch-test
68+
port: 443
69+
matches:
70+
- path:
71+
type: Exact
72+
value: /backendTLSCertMismatch
7473
---
7574
apiVersion: v1
7675
kind: Service
@@ -86,6 +85,36 @@ spec:
8685
port: 443
8786
targetPort: 8443
8887
---
88+
apiVersion: v1
89+
kind: Service
90+
metadata:
91+
name: backendtlspolicy-host-mismatch-test
92+
namespace: gateway-conformance-infra
93+
spec:
94+
selector:
95+
app: backendtlspolicy-test
96+
ports:
97+
- name: "btls"
98+
protocol: TCP
99+
appProtocol: HTTPS
100+
port: 443
101+
targetPort: 8443
102+
---
103+
apiVersion: v1
104+
kind: Service
105+
metadata:
106+
name: backendtlspolicy-cert-mismatch-test
107+
namespace: gateway-conformance-infra
108+
spec:
109+
selector:
110+
app: backendtlspolicy-test
111+
ports:
112+
- name: "btls"
113+
protocol: TCP
114+
appProtocol: HTTPS
115+
port: 443
116+
targetPort: 8443
117+
---
89118
# Deployment must not be applied until after the secret is generated.
90119
apiVersion: apps/v1
91120
kind: Deployment
@@ -151,3 +180,60 @@ spec:
151180
path: crt
152181
- key: tls.key
153182
path: key
183+
---
184+
apiVersion: gateway.networking.k8s.io/v1alpha3
185+
kind: BackendTLSPolicy
186+
metadata:
187+
name: normative-test-backendtlspolicy
188+
namespace: gateway-conformance-infra
189+
spec:
190+
targetRefs:
191+
- group: ""
192+
kind: Service
193+
name: "backendtlspolicy-test"
194+
sectionName: "btls"
195+
validation:
196+
caCertificateRefs:
197+
- group: ""
198+
kind: ConfigMap
199+
# This secret is generated dynamically by the test suite.
200+
name: "backend-tls-checks-certificate"
201+
hostname: "abc.example.com"
202+
---
203+
apiVersion: gateway.networking.k8s.io/v1alpha3
204+
kind: BackendTLSPolicy
205+
metadata:
206+
name: backendtlspolicy-host-mismatch
207+
namespace: gateway-conformance-infra
208+
spec:
209+
targetRefs:
210+
- group: ""
211+
kind: Service
212+
name: "backendtlspolicy-host-mismatch-test"
213+
sectionName: "btls"
214+
validation:
215+
caCertificateRefs:
216+
- group: ""
217+
kind: ConfigMap
218+
# This secret is generated dynamically by the test suite.
219+
name: "backend-tls-checks-certificate"
220+
hostname: "mismatch.example.com"
221+
---
222+
apiVersion: gateway.networking.k8s.io/v1alpha3
223+
kind: BackendTLSPolicy
224+
metadata:
225+
name: backendtlspolicy-cert-mismatch
226+
namespace: gateway-conformance-infra
227+
spec:
228+
targetRefs:
229+
- group: ""
230+
kind: Service
231+
name: "backendtlspolicy-cert-mismatch-test"
232+
sectionName: "btls"
233+
validation:
234+
caCertificateRefs:
235+
- group: ""
236+
kind: ConfigMap
237+
# This secret is generated dynamically by the test suite.
238+
name: "backend-tls-mismatch-certificate"
239+
hostname: "abc.example.com"

conformance/utils/http/http.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripp
117117
WaitForConsistentResponse(t, r, req, expected, timeoutConfig.RequiredConsecutiveSuccesses, timeoutConfig.MaxTimeToConsistency)
118118
}
119119

120+
// MakeRequestAndExpectFailure makes a request with the given parameters.
121+
// This function needs to ensure that after the system is stable the Request is
122+
// not returning http 200 StatusCode.
123+
func MakeRequestAndExpectFailure(t *testing.T, r roundtripper.RoundTripper, timeoutConfig config.TimeoutConfig, gwAddr string, expected ExpectedResponse) {
124+
t.Helper()
125+
126+
req := MakeRequest(t, &expected, gwAddr, "HTTP", "http")
127+
128+
WaitForConsistentFailureResponse(t, r, req, 5, timeoutConfig.MaxTimeToConsistency)
129+
}
130+
120131
func MakeRequest(t *testing.T, expected *ExpectedResponse, gwAddr, protocol, scheme string) roundtripper.Request {
121132
t.Helper()
122133

@@ -261,6 +272,29 @@ func WaitForConsistentResponse(t *testing.T, r roundtripper.RoundTripper, req ro
261272
tlog.Logf(t, "Request passed")
262273
}
263274

275+
// WaitForConsistentFailureResponse repeats the provided request for the given
276+
// period of time and ensures an error is returned each time. This function fails
277+
// when HTTP Status OK (200) is returned.
278+
func WaitForConsistentFailureResponse(t *testing.T, r roundtripper.RoundTripper, req roundtripper.Request, threshold int, maxTimeToConsistency time.Duration) {
279+
AwaitConvergence(t, threshold, maxTimeToConsistency, func(elapsed time.Duration) bool {
280+
_, cRes, err := r.CaptureRoundTrip(req)
281+
if err != nil {
282+
tlog.Logf(t, "Request failed, not ready yet: %v (after %v)", err.Error(), elapsed)
283+
return false
284+
}
285+
if roundtripper.IsTimeoutError(cRes.StatusCode) {
286+
tlog.Logf(t, "Response expectation failed for request: %+v not ready yet: %v (after %v)", req, cRes.StatusCode, elapsed)
287+
return false
288+
}
289+
if cRes.StatusCode == 200 { // http:StatusCode OK
290+
t.Fatalf("Request %+v should failed, returned HTTP Status OK (200) instead", req)
291+
return false
292+
}
293+
return true
294+
})
295+
tlog.Logf(t, "Expectation for failing Request are met")
296+
}
297+
264298
func CompareRoundTrip(t *testing.T, req *roundtripper.Request, cReq *roundtripper.CapturedRequest, cRes *roundtripper.CapturedResponse, expected ExpectedResponse) error {
265299
if roundtripper.IsTimeoutError(cRes.StatusCode) {
266300
if roundtripper.IsTimeoutError(expected.Response.StatusCode) {

conformance/utils/kubernetes/helpers.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737

3838
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
3939
"sigs.k8s.io/gateway-api/apis/v1alpha2"
40+
"sigs.k8s.io/gateway-api/apis/v1alpha3"
4041
"sigs.k8s.io/gateway-api/conformance/utils/config"
4142
"sigs.k8s.io/gateway-api/conformance/utils/tlog"
4243
)
@@ -993,3 +994,35 @@ func findPodConditionInList(t *testing.T, conditions []v1.PodCondition, condName
993994
tlog.Logf(t, "%s was not in conditions list", condName)
994995
return false
995996
}
997+
998+
// BackendTLSPolicyMustHaveCondition checks that the created BackentTLSPolicy has the Condition,
999+
// halting after the specified timeout is exceeded.
1000+
func BackendTLSPolicyMustHaveCondition(t *testing.T, client client.Client, timeoutConfig config.TimeoutConfig, policyNN, gwNN types.NamespacedName, condition metav1.Condition) {
1001+
t.Helper()
1002+
waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeoutConfig.HTTPRouteMustHaveCondition, true, func(ctx context.Context) (bool, error) {
1003+
policy := &v1alpha3.BackendTLSPolicy{}
1004+
err := client.Get(ctx, policyNN, policy)
1005+
if err != nil {
1006+
return false, fmt.Errorf("error fetching BackendTLSPolicy: %w", err)
1007+
}
1008+
1009+
for _, parent := range policy.Status.Ancestors {
1010+
if err := ConditionsHaveLatestObservedGeneration(policy, parent.Conditions); err != nil {
1011+
tlog.Logf(t, "BackendTLSPolicy %s (parentRef=%v) %v",
1012+
policyNN, parentRefToString(parent.AncestorRef), err,
1013+
)
1014+
return false, nil
1015+
}
1016+
1017+
if parent.AncestorRef.Name == gatewayv1.ObjectName(gwNN.Name) && (parent.AncestorRef.Namespace == nil || string(*parent.AncestorRef.Namespace) == gwNN.Namespace) {
1018+
if findConditionInList(t, parent.Conditions, condition.Type, string(condition.Status), condition.Reason) {
1019+
return true, nil
1020+
}
1021+
}
1022+
}
1023+
1024+
return false, nil
1025+
})
1026+
1027+
require.NoErrorf(t, waitErr, "error waiting for BackendTLSPolicy status to have a Condition %v", condition)
1028+
}

conformance/utils/suite/suite.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,8 @@ func (suite *ConformanceTestSuite) Setup(t *testing.T, tests []ConformanceTest)
383383
suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup)
384384
secret = kubernetes.MustCreateSelfSignedCertSecret(t, "gateway-conformance-app-backend", "tls-passthrough-checks-certificate", []string{"abc.example.com"})
385385
suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{secret}, suite.Cleanup)
386+
caConfigMapBST, _, _ := kubernetes.MustCreateCACertConfigMap(t, "gateway-conformance-infra", "backend-tls-mismatch-certificate", []string{"nex.example.com"})
387+
suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{caConfigMapBST}, suite.Cleanup)
386388
caConfigMap, ca, caPrivKey := kubernetes.MustCreateCACertConfigMap(t, "gateway-conformance-infra", "backend-tls-checks-certificate", []string{"abc.example.com"})
387389
suite.Applier.MustApplyObjectsWithCleanup(t, suite.Client, suite.TimeoutConfig, []client.Object{caConfigMap}, suite.Cleanup)
388390
secret = kubernetes.MustCreateCASignedCertSecret(t, "gateway-conformance-infra", "tls-checks-certificate", []string{"abc.example.com"}, ca, caPrivKey)

pkg/features/backendtlspolicy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 The Kubernetes Authors.
2+
Copyright 2025 The Kubernetes Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.

0 commit comments

Comments
 (0)