Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 222 additions & 4 deletions test/kubernetes/e2e/features/routereplacement/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ var (
},
}

// TODO(tim): remove mode here, only test in strict mode.
testModes = map[string]settings.RouteReplacementMode{
"TestStrictModeInvalidPolicyReplacement": settings.RouteReplacementStrict,
"TestStandardModeInvalidPolicyReplacement": settings.RouteReplacementStandard,
"TestStrictModeInvalidMatcherDropsRoute": settings.RouteReplacementStrict,
"TestStrictModeInvalidRouteReplacement": settings.RouteReplacementStrict,
"TestStrictModeInvalidPolicyReplacement": settings.RouteReplacementStrict,
"TestStandardModeInvalidPolicyReplacement": settings.RouteReplacementStandard,
"TestStrictModeInvalidMatcherDropsRoute": settings.RouteReplacementStrict,
"TestStrictModeInvalidRouteReplacement": settings.RouteReplacementStrict,
"TestStrictModeGatewayWideInvalidPolicy": settings.RouteReplacementStrict,
"TestStrictModeListenerSpecificInvalidPolicy": settings.RouteReplacementStrict,
"TestStrictModeListenerSpecificIsolation": settings.RouteReplacementStrict,
}

testCases = map[string]*base.TestCase{
Expand All @@ -54,6 +58,15 @@ var (
"TestStrictModeInvalidRouteReplacement": {
Manifests: []string{strictModeInvalidRouteManifest},
},
"TestStrictModeGatewayWideInvalidPolicy": {
Manifests: []string{gatewayWideInvalidPolicyManifest},
},
"TestStrictModeListenerSpecificInvalidPolicy": {
Manifests: []string{listenerSpecificInvalidPolicyManifest},
},
"TestStrictModeListenerSpecificIsolation": {
Manifests: []string{listenerMergeBlastRadiusManifest},
},
}
)

Expand Down Expand Up @@ -226,6 +239,211 @@ func (s *testingSuite) TestStrictModeInvalidRouteReplacement() {
)
}

// TestStrictModeGatewayWideInvalidPolicy tests that in STRICT mode, when an invalid policy
// is attached to the entire gateway, all routes across all listeners are replaced with 500 responses
func (s *testingSuite) TestStrictModeGatewayWideInvalidPolicy() {
// Verify Gateway shows Accepted=False with GatewayReplaced reason
s.TestInstallation.Assertions.EventuallyGatewayCondition(
s.Ctx,
gatewayWideProxyObjectMeta.Name,
gatewayWideProxyObjectMeta.Namespace,
gwv1.GatewayConditionAccepted,
metav1.ConditionFalse,
)

// Verify both routes still show Accepted=True (routes themselves are valid, gateway policy is invalid)
s.TestInstallation.Assertions.EventuallyHTTPRouteCondition(
s.Ctx,
gatewayWideRoute8080.Name,
gatewayWideRoute8080.Namespace,
gwv1.RouteConditionAccepted,
metav1.ConditionTrue,
)
s.TestInstallation.Assertions.EventuallyHTTPRouteCondition(
s.Ctx,
gatewayWideRoute8081.Name,
gatewayWideRoute8081.Namespace,
gwv1.RouteConditionAccepted,
metav1.ConditionTrue,
)

// Verify that route on port 8080 is replaced with 500 response
s.TestInstallation.Assertions.AssertEventualCurlResponse(
s.Ctx,
testdefaults.CurlPodExecOpt,
[]curl.Option{
curl.WithHost(kubeutils.ServiceFQDN(gatewayWideProxyObjectMeta)),
curl.WithHostHeader("gateway-wide-8080.example.com"),
curl.WithPort(8080),
curl.WithPath("/headers"),
},
&testmatchers.HttpResponse{
StatusCode: http.StatusInternalServerError,
Body: gomega.ContainSubstring(`invalid route configuration detected and replaced with a direct response.`),
},
)

// Verify that route on port 8081 is also replaced with 500 response (gateway-wide effect)
s.TestInstallation.Assertions.AssertEventualCurlResponse(
s.Ctx,
testdefaults.CurlPodExecOpt,
[]curl.Option{
curl.WithHost(kubeutils.ServiceFQDN(gatewayWideProxyObjectMeta)),
curl.WithHostHeader("gateway-wide-8081.example.com"),
curl.WithPort(8081),
curl.WithPath("/headers"),
},
&testmatchers.HttpResponse{
StatusCode: http.StatusInternalServerError,
Body: gomega.ContainSubstring(`invalid route configuration detected and replaced with a direct response.`),
},
)
}

// TestStrictModeListenerSpecificInvalidPolicy tests that in STRICT mode, when an invalid
// policy is attached to a specific listener, only routes on that listener are affected
func (s *testingSuite) TestStrictModeListenerSpecificInvalidPolicy() {
// Verify Gateway itself remains Accepted=True (listener-specific policy doesn't affect gateway)
s.TestInstallation.Assertions.EventuallyGatewayCondition(
s.Ctx,
listenerSpecificProxyObjectMeta.Name,
listenerSpecificProxyObjectMeta.Namespace,
gwv1.GatewayConditionAccepted,
metav1.ConditionTrue,
)

// Verify both routes still show Accepted=True (routes themselves are valid, listener policy is invalid)
s.TestInstallation.Assertions.EventuallyHTTPRouteCondition(
s.Ctx,
listenerAffectedRoute.Name,
listenerAffectedRoute.Namespace,
gwv1.RouteConditionAccepted,
metav1.ConditionTrue,
)
s.TestInstallation.Assertions.EventuallyHTTPRouteCondition(
s.Ctx,
listenerUnaffectedRoute.Name,
listenerUnaffectedRoute.Namespace,
gwv1.RouteConditionAccepted,
metav1.ConditionTrue,
)

// Verify that route on affected listener is replaced with 500 response
s.TestInstallation.Assertions.AssertEventualCurlResponse(
s.Ctx,
testdefaults.CurlPodExecOpt,
[]curl.Option{
curl.WithHost(kubeutils.ServiceFQDN(listenerSpecificProxyObjectMeta)),
curl.WithHostHeader("listener-affected.example.com"),
curl.WithPort(8080),
curl.WithPath("/headers"),
},
&testmatchers.HttpResponse{
StatusCode: http.StatusInternalServerError,
Body: gomega.ContainSubstring(`invalid route configuration detected and replaced with a direct response.`),
},
)

// Verify that route on unaffected listener continues to work normally
s.TestInstallation.Assertions.AssertEventualCurlResponse(
s.Ctx,
testdefaults.CurlPodExecOpt,
[]curl.Option{
curl.WithHost(kubeutils.ServiceFQDN(listenerSpecificProxyObjectMeta)),
curl.WithHostHeader("listener-unaffected.example.com"),
curl.WithPort(8081),
curl.WithPath("/headers"),
},
&testmatchers.HttpResponse{
StatusCode: http.StatusOK,
},
)
}

// TestStrictModeListenerSpecificIsolation tests that in STRICT mode, when listeners share the same
// port and one has an invalid policy, only the specific listener with the invalid policy is affected
// (i.e. no collateral damage to other listeners on same port).
func (s *testingSuite) TestStrictModeListenerSpecificIsolation() {
// Verify Gateway itself remains Accepted=True (listener-specific policy doesn't affect gateway)
s.TestInstallation.Assertions.EventuallyGatewayCondition(
s.Ctx,
listenerIsolationProxyObjectMeta.Name,
listenerIsolationProxyObjectMeta.Namespace,
gwv1.GatewayConditionAccepted,
metav1.ConditionTrue,
)

// Verify all routes still show Accepted=True (routes themselves are valid, listener policy is invalid)
s.TestInstallation.Assertions.EventuallyHTTPRouteCondition(
s.Ctx,
mergeAffectedRoute.Name,
mergeAffectedRoute.Namespace,
gwv1.RouteConditionAccepted,
metav1.ConditionTrue,
)
s.TestInstallation.Assertions.EventuallyHTTPRouteCondition(
s.Ctx,
mergeUnaffectedRoute.Name,
mergeUnaffectedRoute.Namespace,
gwv1.RouteConditionAccepted,
metav1.ConditionTrue,
)
s.TestInstallation.Assertions.EventuallyHTTPRouteCondition(
s.Ctx,
mergeIsolatedRoute.Name,
mergeIsolatedRoute.Namespace,
gwv1.RouteConditionAccepted,
metav1.ConditionTrue,
)

// Verify that route on affected listener (port 8080) is replaced with 500 response
s.TestInstallation.Assertions.AssertEventualCurlResponse(
s.Ctx,
testdefaults.CurlPodExecOpt,
[]curl.Option{
curl.WithHost(kubeutils.ServiceFQDN(listenerIsolationProxyObjectMeta)),
curl.WithHostHeader("affected.example.com"),
curl.WithPort(8080),
curl.WithPath("/headers"),
},
&testmatchers.HttpResponse{
StatusCode: http.StatusInternalServerError,
Body: gomega.ContainSubstring(`invalid route configuration detected and replaced with a direct response.`),
},
)

// Verify that unaffected route on same port (port 8080) continues working normally
// (policy is attached specifically to listener, not port-wide)
s.TestInstallation.Assertions.AssertEventualCurlResponse(
s.Ctx,
testdefaults.CurlPodExecOpt,
[]curl.Option{
curl.WithHost(kubeutils.ServiceFQDN(listenerIsolationProxyObjectMeta)),
curl.WithHostHeader("unaffected.example.com"),
curl.WithPort(8080),
curl.WithPath("/headers"),
},
&testmatchers.HttpResponse{
StatusCode: http.StatusOK,
},
)

// Verify that isolated route on different port (port 8081) continues to work normally
s.TestInstallation.Assertions.AssertEventualCurlResponse(
s.Ctx,
testdefaults.CurlPodExecOpt,
[]curl.Option{
curl.WithHost(kubeutils.ServiceFQDN(listenerIsolationProxyObjectMeta)),
curl.WithHostHeader("isolated.example.com"),
curl.WithPort(8081),
curl.WithPath("/headers"),
},
&testmatchers.HttpResponse{
StatusCode: http.StatusOK,
},
)
}

func (s *testingSuite) patchDeploymentWithMode(mode settings.RouteReplacementMode) {
controllerNamespace := s.TestInstallation.Metadata.InstallNamespace

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
# Gateway with multiple listeners to test gateway-wide invalid policy blast radius
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gw-gateway-wide
namespace: default
spec:
gatewayClassName: kgateway
listeners:
- name: http-8080
port: 8080
protocol: HTTP
allowedRoutes:
namespaces:
from: All
- name: http-8081
port: 8081
protocol: HTTP
allowedRoutes:
namespaces:
from: All
---
# HTTPRoute on first listener
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: route-8080
namespace: default
spec:
parentRefs:
- name: gw-gateway-wide
namespace: default
sectionName: http-8080
hostnames:
- "gateway-wide-8080.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: httpbin
port: 8000
---
# HTTPRoute on second listener
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: route-8081
namespace: default
spec:
parentRefs:
- name: gw-gateway-wide
namespace: default
sectionName: http-8081
hostnames:
- "gateway-wide-8081.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: httpbin
port: 8000
---
# Invalid TrafficPolicy attached to entire gateway (no sectionName)
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: gateway-wide-invalid-policy
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: gw-gateway-wide
transformation:
request:
body:
parseAs: AsJson
value: "{{ invalid_template }" # Invalid template syntax that envoy will reject
Loading
Loading