Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"

// TODO(nfuden): remove once rustformations are able to be used in a production environment
Expand Down Expand Up @@ -249,22 +250,28 @@ func NewPlugin(ctx context.Context, commoncol *collections.CommonCollections, me
Name: policyCR.Name,
}

policyIR, errors := constructor.ConstructIR(krtctx, policyCR)
errs := validateTargetRefsExist(krtctx, policyCR, commoncol)
if len(errs) > 0 {
logger.Error("failed to validate policy targetRef", "policy", policyCR.Name, "namespace", policyCR.Namespace, "errors", errs)
}
policyIR, newErrs := constructor.ConstructIR(krtctx, policyCR)
errs = append(errs, newErrs...)

if err := validateWithValidationLevel(ctx, policyIR, v, commoncol.Settings.ValidationMode); err != nil {
logger.Error("validation failed", "policy", policyCR.Name, "error", err)
errors = append(errors, err)
errs = append(errs, err)
}
precedenceWeight, err := pluginsdkutils.ParsePrecedenceWeightAnnotation(policyCR.Annotations, apiannotations.PolicyPrecedenceWeight)
if err != nil {
errors = append(errors, err)
errs = append(errs, err)
}

pol := &ir.PolicyWrapper{
ObjectSource: objSrc,
Policy: policyCR,
PolicyIR: policyIR,
TargetRefs: pluginsdkutils.TargetRefsToPolicyRefsWithSectionName(policyCR.Spec.TargetRefs, policyCR.Spec.TargetSelectors),
Errors: errors,
Errors: errs,
PrecedenceWeight: precedenceWeight,
}
return pol
Expand Down Expand Up @@ -691,3 +698,78 @@ func (p *trafficPolicyPluginGwPass) handlePerVHostPolicies(
func (p *trafficPolicyPluginGwPass) SupportsPolicyMerge() bool {
return true
}

// validateTargetRefsExist validates that all targetRefs in a TrafficPolicy point to existing resources.
func validateTargetRefsExist(
krtctx krt.HandlerContext,
policyCR *v1alpha1.TrafficPolicy,
commoncol *collections.CommonCollections,
) []error {
// Skip validating global policies with no TargetRefs
if len(policyCR.Spec.TargetRefs) == 0 {
return nil
}

var errs []error

for _, targetRef := range policyCR.Spec.TargetRefs {
kind := string(targetRef.Kind)
ns := policyCR.Namespace
var err error
switch kind {
case wellknown.BackendGVK.Kind:
// Validate Backend exists using BackendIndex
found := false
if commoncol.BackendIndex != nil {
src := ir.ObjectSource{
Group: wellknown.BackendGVK.Group,
Kind: wellknown.BackendGVK.Kind,
Namespace: ns,
Name: string(targetRef.Name),
}
// Backend CRs use port 0 in their resource name
backendResourceName := ir.BackendResourceName(src, 0, "")

for _, backendCol := range commoncol.BackendIndex.Backends() {
if backend := backendCol.GetKey(backendResourceName); backend != nil {
found = true
break
}
}
}
if !found {
err = fmt.Errorf("targetRef Backend %q not found in namespace %q", targetRef.Name, ns)
}
case wellknown.HTTPRouteKind:
err = validateKRTResource(krtctx, commoncol.RawHTTPRoutes, kind, ns, string(targetRef.Name))
case wellknown.GRPCRouteKind:
err = validateKRTResource(krtctx, commoncol.RawGRPCRoutes, kind, ns, string(targetRef.Name))
case wellknown.TCPRouteKind:
err = validateKRTResource(krtctx, commoncol.RawTCPRoutes, kind, ns, string(targetRef.Name))
case wellknown.TLSRouteKind:
err = validateKRTResource(krtctx, commoncol.RawTLSRoutes, kind, ns, string(targetRef.Name))
case wellknown.GatewayKind:
err = validateKRTResource(krtctx, commoncol.RawGateways, kind, ns, string(targetRef.Name))
case wellknown.XListenerSetKind:
err = validateKRTResource(krtctx, commoncol.RawXListenerSets, kind, ns, string(targetRef.Name))
default:
// Unknown target kind - this should be caught by CEL validation
err = fmt.Errorf("targetRef has unsupported kind %s", kind)
}
if err != nil {
errs = append(errs, err)
}
}

return errs
}

// validateKRTResource checks if a resource exists in a KRT collection by name
func validateKRTResource[T any](krtctx krt.HandlerContext, collection krt.Collection[T], kind, namespace, name string) error {
obj := krt.FetchOne(krtctx, collection,
krt.FilterObjectName(types.NamespacedName{Namespace: namespace, Name: name}))
if obj == nil {
return fmt.Errorf("targetRef %s %q not found in namespace %q", kind, name, namespace)
}
return nil
}
4 changes: 4 additions & 0 deletions internal/kgateway/krtcollections/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ func (i *BackendIndex) HasSynced() bool {
return true
}

func (i *BackendIndex) Backends() map[schema.GroupKind]krt.Collection[ir.BackendObjectIR] {
return i.availableBackends
}

func (i *BackendIndex) BackendsWithPolicy() []krt.Collection[*ir.BackendObjectIR] {
return i.availableBackendsWithPolicy
}
Expand Down
6 changes: 4 additions & 2 deletions internal/kgateway/krtcollections/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ func InitCollections(
refgrants *RefGrantIndex,
krtopts krtutil.KrtOptions,
globalSettings apisettings.Settings,
) (*GatewayIndex, *RoutesIndex, *BackendIndex, krt.Collection[ir.EndpointsForBackend]) {
) (*GatewayIndex, *RoutesIndex, *BackendIndex, krt.Collection[ir.EndpointsForBackend], krt.Collection[*gwv1.HTTPRoute],
krt.Collection[*gwv1.GRPCRoute], krt.Collection[*gwv1a2.TCPRoute], krt.Collection[*gwv1a2.TLSRoute],
krt.Collection[*gwv1.Gateway], krt.Collection[*gwxv1a1.XListenerSet]) {
registerTypes(ourClient)

// discovery filter
Expand Down Expand Up @@ -167,7 +169,7 @@ func InitCollections(

gateways := NewGatewayIndex(krtopts, controllerNames, envoyControllerName, policies, kubeRawGateways, kubeRawListenerSets, gatewayClasses, namespaces)
routes := NewRoutesIndex(krtopts, httpRoutes, grpcRoutes, tcproutes, tlsRoutes, policies, backendIndex, refgrants, globalSettings)
return gateways, routes, backendIndex, endpointIRs
return gateways, routes, backendIndex, endpointIRs, httpRoutes, grpcRoutes, tcproutes, tlsRoutes, kubeRawGateways, kubeRawListenerSets
}

func initBackends(plugins sdk.Plugin, backendIndex *BackendIndex) {
Expand Down
11 changes: 11 additions & 0 deletions internal/kgateway/translator/gateway/gateway_translator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,17 @@ func TestBasic(t *testing.T) {
},
})
})

t.Run("Policy with invalid targetref should be rejected", func(t *testing.T) {
test(t, translatorTestCase{
inputFile: "traffic-policy/invalid-targetref.yaml",
outputFile: "traffic-policy/invalid-targetref.yaml",
gwNN: types.NamespacedName{
Namespace: "default",
Name: "example-gateway",
},
})
})
}

func TestValidation(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
# Test file for TrafficPolicy targetRef validation
# This demonstrates fail-closed behavior where policies with invalid targetRefs
# will report errors in their status and not attach to routes
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
spec:
gatewayClassName: kgateway
listeners:
- name: http
protocol: HTTP
port: 80

---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
spec:
parentRefs:
- name: example-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: example-svc
port: 80

---
apiVersion: v1
kind: Service
metadata:
name: example-svc
spec:
selector:
test: test
ports:
- protocol: TCP
port: 80
targetPort: test

---
# TrafficPolicy targeting a non-existent Gateway
# Expected: Policy status should show Accepted: False with error message
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: policy-invalid-gateway
spec:
targetRefs:
- group: gateway.networking.k8s.io
name: non-existent-gateway
kind: Gateway
cors:
allowOrigins:
- "https://ignored.com"

---
# TrafficPolicy targeting a non-existent XListenerSet
# Expected: Policy status should show Accepted: False with error message
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: policy-invalid-xlistenerset
spec:
targetRefs:
- group: gateway.networking.x-k8s.io
name: non-existent-listenerset
kind: XListenerSet
cors:
allowOrigins:
- "https://ignored.com"

---
# TrafficPolicy targeting a valid HTTPRoute
# Expected: Policy status should show Accepted: True and policy should attach
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: policy-valid-httproute
spec:
targetRefs:
- group: gateway.networking.k8s.io
name: example-route
kind: HTTPRoute
cors:
allowOrigins:
- "https://ignored.com"

---
# TrafficPolicy with mixed valid and invalid targetRefs
# Expected: Policy status should show Accepted: False with error for the invalid ref
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: policy-mixed-refs
spec:
targetRefs:
- group: gateway.networking.k8s.io
name: example-route
kind: HTTPRoute
- group: gateway.networking.k8s.io
name: non-existent-route
kind: HTTPRoute
cors:
allowOrigins:
- "https://ignored.com"

---
# TrafficPolicy targeting a non-existent Backend
# Expected: Policy status should show Accepted: False with error message
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: policy-invalid-backend
spec:
targetRefs:
- group: gateway.kgateway.dev
kind: Backend
name: non-existent-backend
cors:
allowOrigins:
- "https://ignored.com"

---
apiVersion: gateway.kgateway.dev/v1alpha1
kind: Backend
metadata:
name: example-backend
spec:
type: AWS
aws:
accountId: "000000000000"
auth:
type: Secret
secretRef:
name: aws-creds
lambda:
functionName: hello-function
qualifier: $LATEST
endpointURL: "http://172.18.0.2:31566"

---
apiVersion: v1
kind: Secret
metadata:
name: aws-creds
type: Opaque
data:
accessKey: QUtJQUlPU0ZPRE5ON0VYQU1QTEU= # Base64 encoded "AKIAIOSFODNN7EXAMPLE"
secretKey: d0phbHJYVXRuRkVNSS9LN01ERU5HL2JQeFJmaUNZRVhBTVBMRUtFWQ== # Base64 encoded "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
Loading
Loading