Repository: envoyproxy/gateway

Issue:
Add support for targeting sections (Gateway listeners) in BackendTrafficPolicy
*Description*:
>Describe the desired behavior, what scenario it enables and how it
would be used.

Relates to https://github.com/envoyproxy/gateway/pull/2053 & https://github.com/envoyproxy/gateway/issues/1821

[optional *Relevant Links*:]
>Any extra documentation required to understand the issue.


Discussion:
@github-actions: This issue has been automatically marked as stale because it has not had activity in the last 30 days.@github-actions: This issue has been automatically marked as stale because it has not had activity in the last 30 days.@github-actions: This issue has been automatically marked as stale because it has not had activity in the last 30 days.

Solution patch:
diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go
index 44fa56887..65a71e90e 100644
--- a/api/v1alpha1/backendtrafficpolicy_types.go
+++ b/api/v1alpha1/backendtrafficpolicy_types.go
@@ -18,7 +18,6 @@ const (
 // +kubebuilder:object:root=true
 // +kubebuilder:resource:shortName=btp
 // +kubebuilder:subresource:status
-// +kubebuilder:subresource:overrideStrategy
 // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].reason`
 // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
 //
@@ -37,8 +36,9 @@ type BackendTrafficPolicy struct {
 
 // spec defines the desired state of BackendTrafficPolicy.
 type BackendTrafficPolicySpec struct {
-
-	// +kubebuilder:validation:XValidation:rule="self.kind == 'Gateway' || self.kind == 'HTTPRoute' || self.kind == 'GRPCRoute' || self.kind == 'UDPRoute' || self.kind == 'TCPRoute' || self.kind == 'TLSRoute'", message="this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute"
+	// +kubebuilder:validation:XValidation:rule="self.group == 'gateway.networking.k8s.io'", message="this policy can only have a targetRef.group of gateway.networking.k8s.io"
+	// +kubebuilder:validation:XValidation:rule="self.kind in ['Gateway', 'HTTPRoute', 'GRPCRoute', 'UDPRoute', 'TCPRoute', 'TLSRoute']", message="this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute"
+	// +kubebuilder:validation:XValidation:rule="!has(self.sectionName)",message="this policy does not yet support the sectionName field"
 	//
 	// targetRef is the name of the resource this policy
 	// is being attached to.
diff --git a/api/v1alpha1/clienttrafficpolicy_types.go b/api/v1alpha1/clienttrafficpolicy_types.go
index 4e8cdd29a..43907dba9 100644
--- a/api/v1alpha1/clienttrafficpolicy_types.go
+++ b/api/v1alpha1/clienttrafficpolicy_types.go
@@ -37,6 +37,10 @@ type ClientTrafficPolicy struct {
 
 // ClientTrafficPolicySpec defines the desired state of ClientTrafficPolicy.
 type ClientTrafficPolicySpec struct {
+	// +kubebuilder:validation:XValidation:rule="self.group == 'gateway.networking.k8s.io'", message="this policy can only have a targetRef.group of gateway.networking.k8s.io"
+	// +kubebuilder:validation:XValidation:rule="self.kind == 'Gateway'", message="this policy can only have a targetRef.kind of Gateway"
+	// +kubebuilder:validation:XValidation:rule="!has(self.sectionName)",message="this policy does not yet support the sectionName field"
+	//
 	// TargetRef is the name of the Gateway resource this policy
 	// is being attached to.
 	// This Policy and the TargetRef MUST be in the same namespace
diff --git a/api/v1alpha1/securitypolicy_types.go b/api/v1alpha1/securitypolicy_types.go
index 7adece4cb..21cac8539 100644
--- a/api/v1alpha1/securitypolicy_types.go
+++ b/api/v1alpha1/securitypolicy_types.go
@@ -36,6 +36,10 @@ type SecurityPolicy struct {
 
 // SecurityPolicySpec defines the desired state of SecurityPolicy.
 type SecurityPolicySpec struct {
+	// +kubebuilder:validation:XValidation:rule="self.group == 'gateway.networking.k8s.io'", message="this policy can only have a targetRef.group of gateway.networking.k8s.io"
+	// +kubebuilder:validation:XValidation:rule="self.kind == 'Gateway'", message="this policy can only have a targetRef.kind of Gateway"
+	// +kubebuilder:validation:XValidation:rule="!has(self.sectionName)",message="this policy does not yet support the sectionName field"
+	//
 	// TargetRef is the name of the Gateway resource this policy
 	// is being attached to.
 	// This Policy and the TargetRef MUST be in the same namespace
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index 8a8e2283d..be8c6c3d7 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -264,10 +264,13 @@ spec:
                 - name
                 type: object
                 x-kubernetes-validations:
+                - message: this policy can only have a targetRef.group of gateway.networking.k8s.io
+                  rule: self.group == 'gateway.networking.k8s.io'
                 - message: this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute
-                  rule: self.kind == 'Gateway' || self.kind == 'HTTPRoute' || self.kind
-                    == 'GRPCRoute' || self.kind == 'UDPRoute' || self.kind == 'TCPRoute'
-                    || self.kind == 'TLSRoute'
+                  rule: self.kind in ['Gateway', 'HTTPRoute', 'GRPCRoute', 'UDPRoute',
+                    'TCPRoute', 'TLSRoute']
+                - message: this policy does not yet support the sectionName field
+                  rule: '!has(self.sectionName)'
             required:
             - targetRef
             type: object
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
index c34aba256..7e0edba92 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
@@ -93,6 +93,13 @@ spec:
                 - kind
                 - name
                 type: object
+                x-kubernetes-validations:
+                - message: this policy can only have a targetRef.group of gateway.networking.k8s.io
+                  rule: self.group == 'gateway.networking.k8s.io'
+                - message: this policy can only have a targetRef.kind of Gateway
+                  rule: self.kind == 'Gateway'
+                - message: this policy does not yet support the sectionName field
+                  rule: '!has(self.sectionName)'
               tcpKeepalive:
                 description: TcpKeepalive settings associated with the downstream
                   client connection. If defined, sets SO_KEEPALIVE on the listener
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
index 2c7c91aa5..544e8dbe8 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml
@@ -235,6 +235,13 @@ spec:
                 - kind
                 - name
                 type: object
+                x-kubernetes-validations:
+                - message: this policy can only have a targetRef.group of gateway.networking.k8s.io
+                  rule: self.group == 'gateway.networking.k8s.io'
+                - message: this policy can only have a targetRef.kind of Gateway
+                  rule: self.kind == 'Gateway'
+                - message: this policy does not yet support the sectionName field
+                  rule: '!has(self.sectionName)'
             required:
             - targetRef
             type: object



Test patch:
diff --git a/test/cel-validation/backendtrafficpolicy_test.go b/test/cel-validation/backendtrafficpolicy_test.go
new file mode 100644
index 000000000..b49870c0d
--- /dev/null
+++ b/test/cel-validation/backendtrafficpolicy_test.go
@@ -0,0 +1,186 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+//go:build celvalidation
+// +build celvalidation
+
+package celvalidation
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+)
+
+func TestBackendTrafficPolicyTarget(t *testing.T) {
+	ctx := context.Background()
+	baseBTP := egv1a1.BackendTrafficPolicy{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "btp",
+			Namespace: metav1.NamespaceDefault,
+		},
+		Spec: egv1a1.BackendTrafficPolicySpec{},
+	}
+
+	sectionName := gwapiv1a2.SectionName("foo")
+
+	cases := []struct {
+		desc         string
+		mutate       func(btp *egv1a1.BackendTrafficPolicy)
+		mutateStatus func(btp *egv1a1.BackendTrafficPolicy)
+		wantErrors   []string
+	}{
+		{
+			desc: "valid gateway targetRef",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+							Kind:  gwapiv1a2.Kind("Gateway"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{},
+		},
+		{
+			desc: "valid httproute targetRef",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+							Kind:  gwapiv1a2.Kind("HTTPRoute"),
+							Name:  gwapiv1a2.ObjectName("httpbin-route"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{},
+		},
+		{
+			desc: "no targetRef",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{}
+			},
+			wantErrors: []string{
+				"spec.targetRef.kind: Invalid value: \"\": spec.targetRef.kind in body should be at least 1 chars long",
+				"spec.targetRef.name: Invalid value: \"\": spec.targetRef.name in body should be at least 1 chars long",
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.group of gateway.networking.k8s.io",
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute",
+			},
+		},
+		{
+			desc: "targetRef unsupported kind",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+							Kind:  gwapiv1a2.Kind("foo"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute",
+			},
+		},
+		{
+			desc: "targetRef unsupported group",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("foo"),
+							Kind:  gwapiv1a2.Kind("Gateway"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.group of gateway.networking.k8s.io",
+			},
+		},
+		{
+			desc: "targetRef unsupported group and kind",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("foo"),
+							Kind:  gwapiv1a2.Kind("bar"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.group of gateway.networking.k8s.io",
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute",
+			},
+		},
+		{
+			desc: "sectionName disabled until supported",
+			mutate: func(btp *egv1a1.BackendTrafficPolicy) {
+				btp.Spec = egv1a1.BackendTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+							Kind:  gwapiv1a2.Kind("Gateway"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+						SectionName: &sectionName,
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy does not yet support the sectionName field",
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.desc, func(t *testing.T) {
+			btp := baseBTP.DeepCopy()
+			btp.Name = fmt.Sprintf("btp-%v", time.Now().UnixNano())
+
+			if tc.mutate != nil {
+				tc.mutate(btp)
+			}
+			err := c.Create(ctx, btp)
+
+			if tc.mutateStatus != nil {
+				tc.mutateStatus(btp)
+				err = c.Status().Update(ctx, btp)
+			}
+
+			if (len(tc.wantErrors) != 0) != (err != nil) {
+				t.Fatalf("Unexpected response while creating BackendTrafficPolicy; got err=\n%v\n;want error=%v", err, tc.wantErrors)
+			}
+
+			var missingErrorStrings []string
+			for _, wantError := range tc.wantErrors {
+				if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(wantError)) {
+					missingErrorStrings = append(missingErrorStrings, wantError)
+				}
+			}
+			if len(missingErrorStrings) != 0 {
+				t.Errorf("Unexpected response while creating BackendTrafficPolicy; got err=\n%v\n;missing strings within error=%q", err, missingErrorStrings)
+			}
+		})
+	}
+}
diff --git a/test/cel-validation/clienttrafficpolicy_test.go b/test/cel-validation/clienttrafficpolicy_test.go
new file mode 100644
index 000000000..b0449b78f
--- /dev/null
+++ b/test/cel-validation/clienttrafficpolicy_test.go
@@ -0,0 +1,170 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+//go:build celvalidation
+// +build celvalidation
+
+package celvalidation
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+)
+
+func TestClientTrafficPolicyTarget(t *testing.T) {
+	ctx := context.Background()
+	baseCTP := egv1a1.ClientTrafficPolicy{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "ctp",
+			Namespace: metav1.NamespaceDefault,
+		},
+		Spec: egv1a1.ClientTrafficPolicySpec{},
+	}
+
+	sectionName := gwapiv1a2.SectionName("foo")
+
+	cases := []struct {
+		desc         string
+		mutate       func(ctp *egv1a1.ClientTrafficPolicy)
+		mutateStatus func(ctp *egv1a1.ClientTrafficPolicy)
+		wantErrors   []string
+	}{
+		{
+			desc: "valid targetRef",
+			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
+				ctp.Spec = egv1a1.ClientTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+							Kind:  gwapiv1a2.Kind("Gateway"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{},
+		},
+		{
+			desc: "no targetRef",
+			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
+				ctp.Spec = egv1a1.ClientTrafficPolicySpec{}
+			},
+			wantErrors: []string{
+				"spec.targetRef.kind: Invalid value: \"\": spec.targetRef.kind in body should be at least 1 chars long",
+				"spec.targetRef.name: Invalid value: \"\": spec.targetRef.name in body should be at least 1 chars long",
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.group of gateway.networking.k8s.io",
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.kind of Gateway",
+			},
+		},
+		{
+			desc: "targetRef unsupported kind",
+			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
+				ctp.Spec = egv1a1.ClientTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+							Kind:  gwapiv1a2.Kind("foo"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.kind of Gateway",
+			},
+		},
+		{
+			desc: "targetRef unsupported group",
+			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
+				ctp.Spec = egv1a1.ClientTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("foo"),
+							Kind:  gwapiv1a2.Kind("Gateway"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.group of gateway.networking.k8s.io",
+			},
+		},
+		{
+			desc: "targetRef unsupported group and kind",
+			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
+				ctp.Spec = egv1a1.ClientTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("foo"),
+							Kind:  gwapiv1a2.Kind("bar"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.group of gateway.networking.k8s.io",
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.kind of Gateway",
+			},
+		},
+		{
+			desc: "sectionName disabled until supported",
+			mutate: func(ctp *egv1a1.ClientTrafficPolicy) {
+				ctp.Spec = egv1a1.ClientTrafficPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+							Kind:  gwapiv1a2.Kind("Gateway"),
+						},
+						SectionName: &sectionName,
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy does not yet support the sectionName field",
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.desc, func(t *testing.T) {
+			ctp := baseCTP.DeepCopy()
+			ctp.Name = fmt.Sprintf("ctp-%v", time.Now().UnixNano())
+
+			if tc.mutate != nil {
+				tc.mutate(ctp)
+			}
+			err := c.Create(ctx, ctp)
+
+			if tc.mutateStatus != nil {
+				tc.mutateStatus(ctp)
+				err = c.Status().Update(ctx, ctp)
+			}
+
+			if (len(tc.wantErrors) != 0) != (err != nil) {
+				t.Fatalf("Unexpected response while creating ClientTrafficPolicy; got err=\n%v\n;want error=%v", err, tc.wantErrors)
+			}
+
+			var missingErrorStrings []string
+			for _, wantError := range tc.wantErrors {
+				if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(wantError)) {
+					missingErrorStrings = append(missingErrorStrings, wantError)
+				}
+			}
+			if len(missingErrorStrings) != 0 {
+				t.Errorf("Unexpected response while creating ClientTrafficPolicy; got err=\n%v\n;missing strings within error=%q", err, missingErrorStrings)
+			}
+		})
+	}
+}
diff --git a/test/cel-validation/securitypolicy_test.go b/test/cel-validation/securitypolicy_test.go
new file mode 100644
index 000000000..7b634f2f1
--- /dev/null
+++ b/test/cel-validation/securitypolicy_test.go
@@ -0,0 +1,171 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+//go:build celvalidation
+// +build celvalidation
+
+package celvalidation
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
+)
+
+func TestSecurityPolicyTarget(t *testing.T) {
+	ctx := context.Background()
+	baseSP := egv1a1.SecurityPolicy{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "sp",
+			Namespace: metav1.NamespaceDefault,
+		},
+		Spec: egv1a1.SecurityPolicySpec{},
+	}
+
+	sectionName := gwapiv1a2.SectionName("foo")
+
+	cases := []struct {
+		desc         string
+		mutate       func(sp *egv1a1.SecurityPolicy)
+		mutateStatus func(sp *egv1a1.SecurityPolicy)
+		wantErrors   []string
+	}{
+		{
+			desc: "valid targetRef",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+							Kind:  gwapiv1a2.Kind("Gateway"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{},
+		},
+		{
+			desc: "no targetRef",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{}
+			},
+			wantErrors: []string{
+				"spec.targetRef.kind: Invalid value: \"\": spec.targetRef.kind in body should be at least 1 chars long",
+				"spec.targetRef.name: Invalid value: \"\": spec.targetRef.name in body should be at least 1 chars long",
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.group of gateway.networking.k8s.io",
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.kind of Gateway",
+			},
+		},
+		{
+			desc: "targetRef unsupported kind",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+							Kind:  gwapiv1a2.Kind("foo"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.kind of Gateway",
+			},
+		},
+		{
+			desc: "targetRef unsupported group",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("foo"),
+							Kind:  gwapiv1a2.Kind("Gateway"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.group of gateway.networking.k8s.io",
+			},
+		},
+		{
+			desc: "targetRef unsupported group and kind",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("foo"),
+							Kind:  gwapiv1a2.Kind("bar"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.group of gateway.networking.k8s.io",
+				"spec.targetRef: Invalid value: \"object\": this policy can only have a targetRef.kind of Gateway",
+			},
+		},
+		{
+			desc: "sectionName disabled until supported",
+			mutate: func(sp *egv1a1.SecurityPolicy) {
+				sp.Spec = egv1a1.SecurityPolicySpec{
+					TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{
+						PolicyTargetReference: gwapiv1a2.PolicyTargetReference{
+							Group: gwapiv1a2.Group("gateway.networking.k8s.io"),
+							Kind:  gwapiv1a2.Kind("Gateway"),
+							Name:  gwapiv1a2.ObjectName("eg"),
+						},
+						SectionName: &sectionName,
+					},
+				}
+			},
+			wantErrors: []string{
+				"spec.targetRef: Invalid value: \"object\": this policy does not yet support the sectionName field",
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.desc, func(t *testing.T) {
+			sp := baseSP.DeepCopy()
+			sp.Name = fmt.Sprintf("sp-%v", time.Now().UnixNano())
+
+			if tc.mutate != nil {
+				tc.mutate(sp)
+			}
+			err := c.Create(ctx, sp)
+
+			if tc.mutateStatus != nil {
+				tc.mutateStatus(sp)
+				err = c.Status().Update(ctx, sp)
+			}
+
+			if (len(tc.wantErrors) != 0) != (err != nil) {
+				t.Fatalf("Unexpected response while creating SecurityPolicy; got err=\n%v\n;want error=%v", err, tc.wantErrors)
+			}
+
+			var missingErrorStrings []string
+			for _, wantError := range tc.wantErrors {
+				if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(wantError)) {
+					missingErrorStrings = append(missingErrorStrings, wantError)
+				}
+			}
+			if len(missingErrorStrings) != 0 {
+				t.Errorf("Unexpected response while creating SecurityPolicy; got err=\n%v\n;missing strings within error=%q", err, missingErrorStrings)
+			}
+		})
+	}
+}