Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
25 changes: 25 additions & 0 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ const (
// to the BackendSecurityPolicy whose targetRefs contains the AIServiceBackend.
k8sClientIndexAIServiceBackendToTargetingBackendSecurityPolicy = "AIServiceBackendToTargetingBackendSecurityPolicy"

// k8sClientIndexReferenceGrantToTargetKind is the index name that maps from namespace/kind to ReferenceGrants, enabling efficient lookup of grants
// allowing access to specific resource types in specific namespaces.
k8sClientIndexReferenceGrantToTargetKind = "ReferenceGrantToTargetKind"

// Indexes for MCP Gateway
//
// k8sClientIndexMCPRouteToAttachedGateway is the index name that maps from a Gateway to the
Expand Down Expand Up @@ -305,6 +309,13 @@ func ApplyIndexing(ctx context.Context, indexer func(ctx context.Context, obj cl
return fmt.Errorf("failed to index field for BackendSecurityPolicy targetRefs: %w", err)
}

// Apply indexes for ReferenceGrant.
err = indexer(ctx, &gwapiv1b1.ReferenceGrant{},
k8sClientIndexReferenceGrantToTargetKind, referenceGrantToTargetKindIndexFunc)
if err != nil {
return fmt.Errorf("failed to create index from target kind to ReferenceGrant: %w", err)
}

// Apply indexes to MCP Gateways.
err = indexer(ctx, &aigv1a1.MCPRoute{},
k8sClientIndexMCPRouteToAttachedGateway, mcpRouteToAttachedGatewayIndexFunc)
Expand Down Expand Up @@ -408,6 +419,20 @@ func getSecretNameAndNamespace(secretRef *gwapiv1.SecretObjectReference, namespa
return fmt.Sprintf("%s.%s", secretRef.Name, namespace)
}

func getReferenceGrantIndexKey(namespace, kind string) string {
return fmt.Sprintf("%s/%s", namespace, kind)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you use the same format as others (name.namespace)

}

func referenceGrantToTargetKindIndexFunc(o client.Object) []string {
referenceGrant := o.(*gwapiv1b1.ReferenceGrant)
var keys []string
for _, to := range referenceGrant.Spec.To {
key := getReferenceGrantIndexKey(referenceGrant.Namespace, string(to.Kind))
keys = append(keys, key)
}
return keys
}

// newConditions creates a new condition with the given type and message.
//
// Currently, we only set one condition at a time either "Accepted" or "NotAccepted".
Expand Down
245 changes: 245 additions & 0 deletions internal/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"

aigv1a1 "github.com/envoyproxy/ai-gateway/api/v1alpha1"
)
Expand Down Expand Up @@ -266,6 +267,250 @@ func Test_getSecretNameAndNamespace(t *testing.T) {
require.Equal(t, "mysecret.foo", getSecretNameAndNamespace(secretRef, "foo"))
}

func Test_referenceGrantToTargetKindIndexFunc(t *testing.T) {
tests := []struct {
name string
referenceGrant *gwapiv1b1.ReferenceGrant
expectedKeys []string
}{
{
name: "single target kind - AIServiceBackend",
referenceGrant: &gwapiv1b1.ReferenceGrant{
ObjectMeta: metav1.ObjectMeta{
Name: "grant1",
Namespace: "backend-ns",
},
Spec: gwapiv1b1.ReferenceGrantSpec{
From: []gwapiv1b1.ReferenceGrantFrom{
{
Group: "aigateway.envoyproxy.io",
Kind: "AIGatewayRoute",
Namespace: "route-ns",
},
},
To: []gwapiv1b1.ReferenceGrantTo{
{
Group: "aigateway.envoyproxy.io",
Kind: "AIServiceBackend",
},
},
},
},
expectedKeys: []string{"backend-ns/AIServiceBackend"},
},
{
name: "multiple target kinds",
referenceGrant: &gwapiv1b1.ReferenceGrant{
ObjectMeta: metav1.ObjectMeta{
Name: "grant2",
Namespace: "backend-ns",
},
Spec: gwapiv1b1.ReferenceGrantSpec{
From: []gwapiv1b1.ReferenceGrantFrom{
{
Group: "aigateway.envoyproxy.io",
Kind: "AIGatewayRoute",
Namespace: "route-ns",
},
},
To: []gwapiv1b1.ReferenceGrantTo{
{
Group: "aigateway.envoyproxy.io",
Kind: "AIServiceBackend",
},
{
Group: "",
Kind: "Secret",
},
},
},
},
expectedKeys: []string{"backend-ns/AIServiceBackend", "backend-ns/Secret"},
},
{
name: "empty group for core resources",
referenceGrant: &gwapiv1b1.ReferenceGrant{
ObjectMeta: metav1.ObjectMeta{
Name: "grant3",
Namespace: "other-ns",
},
Spec: gwapiv1b1.ReferenceGrantSpec{
From: []gwapiv1b1.ReferenceGrantFrom{
{
Group: "gateway.networking.k8s.io",
Kind: "HTTPRoute",
Namespace: "route-ns",
},
},
To: []gwapiv1b1.ReferenceGrantTo{
{
Group: "",
Kind: "Service",
},
},
},
},
expectedKeys: []string{"other-ns/Service"},
},
{
name: "no target kinds",
referenceGrant: &gwapiv1b1.ReferenceGrant{
ObjectMeta: metav1.ObjectMeta{
Name: "grant4",
Namespace: "backend-ns",
},
Spec: gwapiv1b1.ReferenceGrantSpec{
From: []gwapiv1b1.ReferenceGrantFrom{
{
Group: "aigateway.envoyproxy.io",
Kind: "AIGatewayRoute",
Namespace: "route-ns",
},
},
To: []gwapiv1b1.ReferenceGrantTo{},
},
},
expectedKeys: nil, // nil is returned for empty To array
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
keys := referenceGrantToTargetKindIndexFunc(tt.referenceGrant)
require.Equal(t, tt.expectedKeys, keys)
})
}
}

func Test_referenceGrantIndexWithQuery(t *testing.T) {
// Create a fake client with the ReferenceGrant index
c := fake.NewClientBuilder().
WithScheme(Scheme).
WithIndex(&gwapiv1b1.ReferenceGrant{}, k8sClientIndexReferenceGrantToTargetKind, referenceGrantToTargetKindIndexFunc).
Build()

// Create multiple ReferenceGrants with different target kinds
grant1 := &gwapiv1b1.ReferenceGrant{
ObjectMeta: metav1.ObjectMeta{
Name: "grant-aiservicebackend",
Namespace: "backend-ns",
},
Spec: gwapiv1b1.ReferenceGrantSpec{
From: []gwapiv1b1.ReferenceGrantFrom{
{
Group: "aigateway.envoyproxy.io",
Kind: "AIGatewayRoute",
Namespace: "route-ns",
},
},
To: []gwapiv1b1.ReferenceGrantTo{
{
Group: "aigateway.envoyproxy.io",
Kind: "AIServiceBackend",
},
},
},
}

grant2 := &gwapiv1b1.ReferenceGrant{
ObjectMeta: metav1.ObjectMeta{
Name: "grant-secret",
Namespace: "backend-ns",
},
Spec: gwapiv1b1.ReferenceGrantSpec{
From: []gwapiv1b1.ReferenceGrantFrom{
{
Group: "gateway.networking.k8s.io",
Kind: "HTTPRoute",
Namespace: "route-ns",
},
},
To: []gwapiv1b1.ReferenceGrantTo{
{
Group: "",
Kind: "Secret",
},
},
},
}

grant3 := &gwapiv1b1.ReferenceGrant{
ObjectMeta: metav1.ObjectMeta{
Name: "grant-multiple",
Namespace: "backend-ns",
},
Spec: gwapiv1b1.ReferenceGrantSpec{
From: []gwapiv1b1.ReferenceGrantFrom{
{
Group: "aigateway.envoyproxy.io",
Kind: "AIGatewayRoute",
Namespace: "route-ns",
},
},
To: []gwapiv1b1.ReferenceGrantTo{
{
Group: "aigateway.envoyproxy.io",
Kind: "AIServiceBackend",
},
{
Group: "",
Kind: "Secret",
},
},
},
}

require.NoError(t, c.Create(t.Context(), grant1))
require.NoError(t, c.Create(t.Context(), grant2))
require.NoError(t, c.Create(t.Context(), grant3))

t.Run("query for AIServiceBackend grants in backend-ns", func(t *testing.T) {
var grants gwapiv1b1.ReferenceGrantList
err := c.List(t.Context(), &grants,
client.MatchingFields{k8sClientIndexReferenceGrantToTargetKind: "backend-ns/AIServiceBackend"})
require.NoError(t, err)

// Should find grant1 and grant3 (both allow AIServiceBackend in backend-ns)
require.Len(t, grants.Items, 2)
names := []string{grants.Items[0].Name, grants.Items[1].Name}
require.Contains(t, names, "grant-aiservicebackend")
require.Contains(t, names, "grant-multiple")
})

t.Run("query for Secret grants in backend-ns", func(t *testing.T) {
var grants gwapiv1b1.ReferenceGrantList
err := c.List(t.Context(), &grants,
client.MatchingFields{k8sClientIndexReferenceGrantToTargetKind: "backend-ns/Secret"})
require.NoError(t, err)

// Should find grant2 and grant3 (both allow Secret in backend-ns)
require.Len(t, grants.Items, 2)
names := []string{grants.Items[0].Name, grants.Items[1].Name}
require.Contains(t, names, "grant-secret")
require.Contains(t, names, "grant-multiple")
})

t.Run("query for non-existent kind", func(t *testing.T) {
var grants gwapiv1b1.ReferenceGrantList
err := c.List(t.Context(), &grants,
client.MatchingFields{k8sClientIndexReferenceGrantToTargetKind: "backend-ns/NonExistentKind"})
require.NoError(t, err)

// Should find nothing
require.Empty(t, grants.Items)
})

t.Run("query with wrong namespace", func(t *testing.T) {
var grants gwapiv1b1.ReferenceGrantList
err := c.List(t.Context(), &grants,
client.MatchingFields{k8sClientIndexReferenceGrantToTargetKind: "wrong-ns/AIServiceBackend"})
require.NoError(t, err)

// Should find nothing (wrong namespace)
require.Empty(t, grants.Items)
})
}

func Test_handleFinalizer(t *testing.T) {
tests := []struct {
name string
Expand Down
9 changes: 6 additions & 3 deletions internal/controller/referencegrant.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,13 @@ func (v *referenceGrantValidator) validateAIServiceBackendReference(
return nil
}

// List all ReferenceGrants in the backend namespace
indexKey := getReferenceGrantIndexKey(backendNamespace, aiServiceBackendKind)
var referenceGrants gwapiv1b1.ReferenceGrantList
if err := v.client.List(ctx, &referenceGrants, client.InNamespace(backendNamespace)); err != nil {
return fmt.Errorf("failed to list ReferenceGrants in namespace %s: %w", backendNamespace, err)
if err := v.client.List(ctx, &referenceGrants,
client.MatchingFields{k8sClientIndexReferenceGrantToTargetKind: indexKey},
); err != nil {
return fmt.Errorf("failed to list ReferenceGrants in namespace %s for kind %s: %w",
backendNamespace, aiServiceBackendKind, err)
}

// Check if any ReferenceGrant allows this cross-namespace reference
Expand Down
Loading
Loading