Skip to content
Draft
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
143 changes: 143 additions & 0 deletions controller/hybridgateway/converter/http_route_converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
Expand Down Expand Up @@ -443,6 +444,148 @@ func TestHTTPRouteConverter_Translate(t *testing.T) {
assert.Equal(t, pluginObj.Name, bindingObj.Spec.PluginReference.Name)
},
},
{
name: "translates partially invalid cross-namespace sibling backends",
setup: func() *httpRouteConverter {
v1Name := gwtypes.ObjectName("app-backend-v1")
v2Name := gwtypes.ObjectName("app-backend-v2")
serviceKind := gwtypes.Kind("Service")
serviceGroup := gwtypes.Group("")

route := newHTTPRouteWithRules(
[]string{"api.example.com"},
[]gwtypes.HTTPRouteRule{
{
Matches: []gwtypes.HTTPRouteMatch{{
Path: &gatewayv1.HTTPPathMatch{
Type: new(gatewayv1.PathMatchPathPrefix),
Value: new("/v2"),
},
}},
BackendRefs: []gwtypes.HTTPBackendRef{{
BackendRef: gwtypes.BackendRef{
BackendObjectReference: gwtypes.BackendObjectReference{
Name: v2Name,
Namespace: ptr.To(gwtypes.Namespace("app-backend")),
Kind: &serviceKind,
Group: &serviceGroup,
Port: new(gwtypes.PortNumber(80)),
},
},
}},
},
{
Matches: []gwtypes.HTTPRouteMatch{{
Path: &gatewayv1.HTTPPathMatch{
Type: new(gatewayv1.PathMatchPathPrefix),
Value: new("/"),
},
}},
BackendRefs: []gwtypes.HTTPBackendRef{{
BackendRef: gwtypes.BackendRef{
BackendObjectReference: gwtypes.BackendObjectReference{
Name: v1Name,
Namespace: ptr.To(gwtypes.Namespace("app-backend")),
Kind: &serviceKind,
Group: &serviceGroup,
Port: new(gwtypes.PortNumber(80)),
},
},
}},
},
},
)

gateway := baseGateway()
objects := append(
newKonnectGatewayStandardObjects(gateway),
newNamespace(),
&corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "app-backend-v1", Namespace: "app-backend"},
Spec: corev1.ServiceSpec{
ClusterIP: "10.0.0.2",
Ports: []corev1.ServicePort{{
Name: "http",
Port: 80,
Protocol: corev1.ProtocolTCP,
TargetPort: intstr.FromInt(8080),
}},
},
},
&corev1.Service{
ObjectMeta: metav1.ObjectMeta{Name: "app-backend-v2", Namespace: "app-backend"},
Spec: corev1.ServiceSpec{
ClusterIP: "10.0.0.3",
Ports: []corev1.ServicePort{{
Name: "http",
Port: 80,
Protocol: corev1.ProtocolTCP,
TargetPort: intstr.FromInt(8080),
}},
},
},
newEndpointSlice("app-backend-v1", "app-backend", 8080, []string{"10.0.1.1"}),
&gwtypes.ReferenceGrant{
ObjectMeta: metav1.ObjectMeta{Name: "allow-app-backend-v1", Namespace: "app-backend"},
Spec: gwtypes.ReferenceGrantSpec{
From: []gwtypes.ReferenceGrantFrom{{
Group: gwtypes.GroupName,
Kind: "HTTPRoute",
Namespace: "default",
}},
To: []gwtypes.ReferenceGrantTo{{
Group: gwtypes.Group(""),
Kind: gwtypes.Kind("Service"),
Name: &v1Name,
}},
},
},
)
fakeClient := fake.NewClientBuilder().WithScheme(scheme.Get()).WithObjects(objects...).Build()
return newHTTPRouteConverter(route, fakeClient, false, "").(*httpRouteConverter)
},
wantCount: 9,
wantOutputs: outputCount{
upstreams: 2,
services: 2,
routes: 2,
targets: 1,
bindings: 1,
plugins: 1,
},
wantStoreLen: 9,
assertFn: func(t *testing.T, store []client.Object) {
t.Helper()

var (
targets []*configurationv1alpha1.KongTarget
pluginObj *configurationv1.KongPlugin
bindingObj *configurationv1alpha1.KongPluginBinding
)

for _, obj := range store {
switch typed := obj.(type) {
case *configurationv1alpha1.KongTarget:
targets = append(targets, typed)
case *configurationv1.KongPlugin:
pluginObj = typed
case *configurationv1alpha1.KongPluginBinding:
bindingObj = typed
}
}

require.Len(t, targets, 1)
assert.Equal(t, "10.0.1.1:8080", targets[0].Spec.Target)

require.NotNil(t, pluginObj)
require.NotNil(t, bindingObj)
assert.Equal(t, "request-termination", pluginObj.PluginName)

var config map[string]any
require.NoError(t, json.Unmarshal(pluginObj.Config.Raw, &config))
assert.Equal(t, "no existing backendRef provided", config["message"])
},
},
{
name: "translates multi rule redirect only route end to end",
setup: func() *httpRouteConverter {
Expand Down
69 changes: 69 additions & 0 deletions controller/hybridgateway/route/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2059,6 +2059,75 @@ func TestBuildResolvedRefsCondition(t *testing.T) {
wantReason: string(gwtypes.RouteReasonResolvedRefs),
wantMsgPart: "All references resolved",
},
{
name: "partially invalid cross-namespace sibling backends still report ref not permitted",
clientObjs: []client.Object{
&corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: "app-backend",
Name: "app-backend-v1",
},
},
&corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: "app-backend",
Name: "app-backend-v2",
},
},
&gwtypes.ReferenceGrant{
ObjectMeta: metav1.ObjectMeta{
Namespace: "app-backend",
Name: "grant-v1-only",
},
Spec: gwtypes.ReferenceGrantSpec{
From: []gwtypes.ReferenceGrantFrom{{
Group: gwtypes.GroupName,
Kind: "HTTPRoute",
Namespace: gwtypes.Namespace("default"),
}},
To: []gwtypes.ReferenceGrantTo{{
Group: gwtypes.Group("core"),
Kind: gwtypes.Kind("Service"),
Name: ptrObjName("app-backend-v1"),
}},
},
},
},
route: &gwtypes.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "route"},
Spec: gwtypes.HTTPRouteSpec{
Rules: []gwtypes.HTTPRouteRule{
{
BackendRefs: []gwtypes.HTTPBackendRef{{
BackendRef: gwtypes.BackendRef{
BackendObjectReference: gwtypes.BackendObjectReference{
Name: gwtypes.ObjectName("app-backend-v2"),
Kind: kindPtr("Service"),
Group: groupPtr("core"),
Namespace: nsPtr("app-backend"),
},
},
}},
},
{
BackendRefs: []gwtypes.HTTPBackendRef{{
BackendRef: gwtypes.BackendRef{
BackendObjectReference: gwtypes.BackendObjectReference{
Name: gwtypes.ObjectName("app-backend-v1"),
Kind: kindPtr("Service"),
Group: groupPtr("core"),
Namespace: nsPtr("app-backend"),
},
},
}},
},
},
},
},
wantStatus: metav1.ConditionFalse,
wantReason: string(gwtypes.RouteReasonRefNotPermitted),
wantMsgPart: "not permitted by any ReferenceGrant",
},
{
name: "multiple refs, first fails",
clientObjs: []client.Object{serviceDefault},
Expand Down
73 changes: 73 additions & 0 deletions controller/hybridgateway/target/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2680,6 +2680,79 @@ func TestTargetsForBackendRefs(t *testing.T) {
expectedTargets: 1,
expectedError: false,
},
{
name: "Mixed cross-namespace backend refs should keep permitted sibling targets",
httpRoute: createTestHTTPRoute("test-route", "frontend-ns", []gwtypes.HTTPBackendRef{
createTestHTTPBackendRef("app-backend-v2", "backend-ns", nil, ptr.To[int32](80)),
createTestHTTPBackendRef("app-backend-v1", "backend-ns", nil, ptr.To[int32](80)),
}),
backendRefs: []gwtypes.HTTPBackendRef{
createTestHTTPBackendRef("app-backend-v2", "backend-ns", nil, ptr.To[int32](80)),
createTestHTTPBackendRef("app-backend-v1", "backend-ns", nil, ptr.To[int32](80)),
},
pRef: &gwtypes.ParentReference{Name: "test-gateway"},
upstreamName: "test-upstream",
fqdn: false,
services: []corev1.Service{
{
ObjectMeta: metav1.ObjectMeta{Name: "app-backend-v1", Namespace: "backend-ns"},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{{Name: "http", Port: 80, Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromInt(8080)}},
Type: corev1.ServiceTypeClusterIP,
},
},
{
ObjectMeta: metav1.ObjectMeta{Name: "app-backend-v2", Namespace: "backend-ns"},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{{Name: "http", Port: 80, Protocol: corev1.ProtocolTCP, TargetPort: intstr.FromInt(8080)}},
Type: corev1.ServiceTypeClusterIP,
},
},
},
endpointSlices: []discoveryv1.EndpointSlice{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-backend-v1-slice",
Namespace: "backend-ns",
Labels: map[string]string{
"kubernetes.io/service-name": "app-backend-v1",
},
},
Ports: []discoveryv1.EndpointPort{createTestEndpointPort("http", 8080, corev1.ProtocolTCP)},
Endpoints: []discoveryv1.Endpoint{createTestEndpoint([]string{"10.0.3.1"}, true)},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-backend-v2-slice",
Namespace: "backend-ns",
Labels: map[string]string{
"kubernetes.io/service-name": "app-backend-v2",
},
},
Ports: []discoveryv1.EndpointPort{createTestEndpointPort("http", 8080, corev1.ProtocolTCP)},
Endpoints: []discoveryv1.Endpoint{createTestEndpoint([]string{"10.0.3.2"}, true)},
},
},
referenceGrants: []gwtypes.ReferenceGrant{
{
ObjectMeta: metav1.ObjectMeta{Name: "allow-frontend-to-v1", Namespace: "backend-ns"},
Spec: gwtypes.ReferenceGrantSpec{
From: []gwtypes.ReferenceGrantFrom{
{Group: gwtypes.GroupName, Kind: "HTTPRoute", Namespace: "frontend-ns"},
},
To: []gwtypes.ReferenceGrantTo{
{Group: "", Kind: "Service", Name: ptr.To(gatewayv1.ObjectName("app-backend-v1"))},
},
},
},
},
expectedTargets: 1,
expectedError: false,
validateResult: func(t *testing.T, targets []configurationv1alpha1.KongTarget) {
require.Len(t, targets, 1)
assert.Equal(t, "10.0.3.1:8080", targets[0].Spec.Target)
},
},
{
name: "Cross-namespace backend refs without ReferenceGrant should fail",
httpRoute: createTestHTTPRoute("test-route", "frontend-ns", []gwtypes.HTTPBackendRef{
Expand Down
1 change: 0 additions & 1 deletion test/conformance/skipped_tests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ var skippedTestsForHybrid = []string{
tests.HTTPRouteInvalidBackendRefUnknownKind.ShortName,
tests.HTTPRouteMethodMatching.ShortName,
tests.HTTPRouteMatchingAcrossRoutes.ShortName,
tests.HTTPRoutePartiallyInvalidViaInvalidReferenceGrant.ShortName,
tests.HTTPRoutePathMatchOrder.ShortName,
tests.HTTPRouteQueryParamMatching.ShortName,
tests.HTTPRouteExactPathMatching.ShortName,
Expand Down
Loading