diff --git a/api/v1alpha1/securitypolicy_types.go b/api/v1alpha1/securitypolicy_types.go new file mode 100644 index 000000000000..f53d237323ad --- /dev/null +++ b/api/v1alpha1/securitypolicy_types.go @@ -0,0 +1,68 @@ +// 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. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" +) + +const ( + // KindSecurityPolicy is the name of the SecurityPolicy kind. + KindSecurityPolicy = "SecurityPolicy" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].reason` +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` + +// SecurityPolicy allows the user to configure various security settings for a +// Gateway. +type SecurityPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of SecurityPolicy. + Spec SecurityPolicySpec `json:"spec"` + + // Status defines the current status of SecurityPolicy. + Status SecurityPolicyStatus `json:"status,omitempty"` +} + +// SecurityPolicySpec defines the desired state of SecurityPolicy. +type SecurityPolicySpec struct { + // 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 + // for this Policy to have effect and be applied to the Gateway. + // TargetRef + TargetRef gwapiv1a2.PolicyTargetReferenceWithSectionName `json:"targetRef"` +} + +// SecurityPolicyStatus defines the state of SecurityPolicy +type SecurityPolicyStatus struct { + // Conditions describe the current conditions of the SecurityPolicy. + // + // +optional + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MaxItems=8 + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +//+kubebuilder:object:root=true + +// SecurityPolicyList contains a list of SecurityPolicy resources. +type SecurityPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []SecurityPolicy `json:"items"` +} + +func init() { + SchemeBuilder.Register(&SecurityPolicy{}, &SecurityPolicyList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b0a1f4f18c06..77f9f6efdb5c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1854,6 +1854,103 @@ func (in *RequestHeaderCustomTag) DeepCopy() *RequestHeaderCustomTag { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicy) DeepCopyInto(out *SecurityPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicy. +func (in *SecurityPolicy) DeepCopy() *SecurityPolicy { + if in == nil { + return nil + } + out := new(SecurityPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SecurityPolicy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicyList) DeepCopyInto(out *SecurityPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SecurityPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicyList. +func (in *SecurityPolicyList) DeepCopy() *SecurityPolicyList { + if in == nil { + return nil + } + out := new(SecurityPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SecurityPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicySpec) DeepCopyInto(out *SecurityPolicySpec) { + *out = *in + in.TargetRef.DeepCopyInto(&out.TargetRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicySpec. +func (in *SecurityPolicySpec) DeepCopy() *SecurityPolicySpec { + if in == nil { + return nil + } + out := new(SecurityPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecurityPolicyStatus) DeepCopyInto(out *SecurityPolicyStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityPolicyStatus. +func (in *SecurityPolicyStatus) DeepCopy() *SecurityPolicyStatus { + if in == nil { + return nil + } + out := new(SecurityPolicyStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SourceMatch) DeepCopyInto(out *SourceMatch) { *out = *in diff --git a/internal/gatewayapi/resource.go b/internal/gatewayapi/resource.go index b27e78614dcb..e8a1a80def5d 100644 --- a/internal/gatewayapi/resource.go +++ b/internal/gatewayapi/resource.go @@ -45,6 +45,7 @@ type Resources struct { ExtensionRefFilters []unstructured.Unstructured `json:"extensionRefFilters,omitempty" yaml:"extensionRefFilters,omitempty"` EnvoyPatchPolicies []*egv1a1.EnvoyPatchPolicy `json:"envoyPatchPolicies,omitempty" yaml:"envoyPatchPolicies,omitempty"` ClientTrafficPolicies []*egv1a1.ClientTrafficPolicy `json:"clientTrafficPolicies,omitempty" yaml:"clientTrafficPolicies,omitempty"` + SecurityPolicies []*egv1a1.SecurityPolicy `json:"securityPolicies,omitempty" yaml:"securityPolicies,omitempty"` } func NewResources() *Resources { @@ -63,6 +64,7 @@ func NewResources() *Resources { ExtensionRefFilters: []unstructured.Unstructured{}, EnvoyPatchPolicies: []*egv1a1.EnvoyPatchPolicy{}, ClientTrafficPolicies: []*egv1a1.ClientTrafficPolicy{}, + SecurityPolicies: []*egv1a1.SecurityPolicy{}, } } diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index 1c2cef285514..47b291ea800e 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -152,7 +152,11 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { key := utils.NamespacedName(clientTrafficPolicy) r.ProviderResources.ClientTrafficPolicyStatuses.Store(key, &clientTrafficPolicy.Status) } - + for _, securityPolicy := range result.SecurityPolicies { + securityPolicy := securityPolicy + key := utils.NamespacedName(securityPolicy) + r.ProviderResources.SecurityPolicyStatuses.Store(key, &securityPolicy.Status) + } }, ) r.Logger.Info("shutting down") diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go new file mode 100644 index 000000000000..61a22dcb9577 --- /dev/null +++ b/internal/gatewayapi/securitypolicy.go @@ -0,0 +1,250 @@ +// 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. + +package gatewayapi + +import ( + "fmt" + "sort" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/status" + "github.com/envoyproxy/gateway/internal/utils/ptr" +) + +func securityPolicyHasSectionName(policy *egv1a1.SecurityPolicy) bool { + return policy.Spec.TargetRef.SectionName != nil +} + +func ProcessSecurityPolicies(securityPolicies []*egv1a1.SecurityPolicy, + gateways []*GatewayContext, + xdsIR XdsIRMap) []*egv1a1.SecurityPolicy { + var res []*egv1a1.SecurityPolicy + + // Sort based on timestamp + sort.Slice(securityPolicies, func(i, j int) bool { + return securityPolicies[i].CreationTimestamp.Before(&(securityPolicies[j].CreationTimestamp)) + }) + + policyMap := make(map[types.NamespacedName]sets.Set[string]) + + // Translate + // 1. First translate Policies with a sectionName set + // 2. Then loop again and translate the policies without a sectionName + // TODO: Import sort order to ensure policy with same section always appear + // before policy with no section so below loops can be flattened into 1. + + for _, policy := range securityPolicies { + if securityPolicyHasSectionName(policy) { + policy := policy.DeepCopy() + res = append(res, policy) + + gateway := getSecurityGatewayTargetRef(policy, gateways) + + // Negative statuses have already been assigned so its safe to skip + if gateway == nil { + continue + } + + // Check for conflicts + key := types.NamespacedName{ + Name: gateway.Name, + Namespace: gateway.Namespace, + } + + // Check if another policy targeting the same section exists + section := string(*(policy.Spec.TargetRef.SectionName)) + s, ok := policyMap[key] + if ok && s.Has(section) { + message := "Unable to target section, another ClientTrafficPolicy has already attached to it" + + status.SetSecurityPolicyCondition(policy, + gwv1a2.PolicyConditionAccepted, + metav1.ConditionFalse, + gwv1a2.PolicyReasonConflicted, + message, + ) + continue + } + + // Add section to policy map + if s == nil { + policyMap[key] = sets.New[string]() + } + policyMap[key].Insert(section) + + translateSecurityPolicy(policy, xdsIR) + + // Set Accepted=True + status.SetSecurityPolicyCondition(policy, + gwv1a2.PolicyConditionAccepted, + metav1.ConditionTrue, + gwv1a2.PolicyReasonAccepted, + "ClientTrafficPolicy has been accepted.", + ) + } + } + + // Policy with no section set (targeting all sections) + for _, policy := range securityPolicies { + if !securityPolicyHasSectionName(policy) { + + policy := policy.DeepCopy() + res = append(res, policy) + + gateway := getSecurityGatewayTargetRef(policy, gateways) + + // Negative statuses have already been assigned so its safe to skip + if gateway == nil { + continue + } + + // Check for conflicts + key := types.NamespacedName{ + Name: gateway.Name, + Namespace: gateway.Namespace, + } + s, ok := policyMap[key] + // Check if another policy targeting the same Gateway exists + if ok && s.Has(AllSections) { + message := "Unable to target Gateway, another ClientTrafficPolicy has already attached to it" + + status.SetSecurityPolicyCondition(policy, + gwv1a2.PolicyConditionAccepted, + metav1.ConditionFalse, + gwv1a2.PolicyReasonConflicted, + message, + ) + + continue + + } + + // Check if another policy targeting the same Gateway exists + if ok && (s.Len() > 0) { + // Maintain order here to ensure status/string does not change with same data + sections := s.UnsortedList() + sort.Strings(sections) + message := fmt.Sprintf("There are existing ClientTrafficPolicies that are overriding these sections %v", sections) + + status.SetSecurityPolicyCondition(policy, + egv1a1.PolicyConditionOverridden, + metav1.ConditionTrue, + egv1a1.PolicyReasonOverridden, + message, + ) + } + + // Add section to policy map + if s == nil { + policyMap[key] = sets.New[string]() + } + policyMap[key].Insert(AllSections) + + translateSecurityPolicy(policy, xdsIR) + + // Set Accepted=True + status.SetSecurityPolicyCondition(policy, + gwv1a2.PolicyConditionAccepted, + metav1.ConditionTrue, + gwv1a2.PolicyReasonAccepted, + "ClientTrafficPolicy has been accepted.", + ) + } + } + + return res +} + +func getSecurityGatewayTargetRef(policy *egv1a1.SecurityPolicy, gateways []*GatewayContext) *GatewayContext { + targetNs := policy.Spec.TargetRef.Namespace + // If empty, default to namespace of policy + if targetNs == nil { + targetNs = ptr.To(gwv1b1.Namespace(policy.Namespace)) + } + + // Ensure policy can only target a Gateway + if policy.Spec.TargetRef.Group != gwv1b1.GroupName || policy.Spec.TargetRef.Kind != KindGateway { + message := fmt.Sprintf("TargetRef.Group:%s TargetRef.Kind:%s, only TargetRef.Group:%s and TargetRef.Kind:%s is supported.", + policy.Spec.TargetRef.Group, policy.Spec.TargetRef.Kind, gwv1b1.GroupName, KindGateway) + + status.SetSecurityPolicyCondition(policy, + gwv1a2.PolicyConditionAccepted, + metav1.ConditionFalse, + gwv1a2.PolicyReasonInvalid, + message, + ) + return nil + } + + // Ensure Policy and target Gateway are in the same namespace + if policy.Namespace != string(*targetNs) { + + message := fmt.Sprintf("Namespace:%s TargetRef.Namespace:%s, ClientTrafficPolicy can only target a Gateway in the same namespace.", + policy.Namespace, *targetNs) + status.SetSecurityPolicyCondition(policy, + gwv1a2.PolicyConditionAccepted, + metav1.ConditionFalse, + gwv1a2.PolicyReasonInvalid, + message, + ) + return nil + } + + // Find the Gateway + var gateway *GatewayContext + for _, g := range gateways { + if g.Name == string(policy.Spec.TargetRef.Name) && g.Namespace == string(*targetNs) { + gateway = g + break + } + } + + // Gateway not found + if gateway == nil { + message := fmt.Sprintf("Gateway:%s not found.", policy.Spec.TargetRef.Name) + + status.SetSecurityPolicyCondition(policy, + gwv1a2.PolicyConditionAccepted, + metav1.ConditionFalse, + gwv1a2.PolicyReasonTargetNotFound, + message, + ) + return nil + } + + // If sectionName is set, make sure its valid + if policy.Spec.TargetRef.SectionName != nil { + found := false + for _, l := range gateway.Spec.Listeners { + if l.Name == *(policy.Spec.TargetRef.SectionName) { + found = true + break + } + } + if !found { + message := fmt.Sprintf("SectionName(Listener):%s not found.", *(policy.Spec.TargetRef.SectionName)) + status.SetSecurityPolicyCondition(policy, + gwv1a2.PolicyConditionAccepted, + metav1.ConditionFalse, + gwv1a2.PolicyReasonTargetNotFound, + message, + ) + return nil + } + } + + return gateway +} + +func translateSecurityPolicy(policy *egv1a1.SecurityPolicy, xdsIR XdsIRMap) { + // TODO +} diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 13bee453da18..46502f9e8791 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -92,6 +92,7 @@ func newTranslateResult(gateways []*GatewayContext, tcpRoutes []*TCPRouteContext, udpRoutes []*UDPRouteContext, clientTrafficPolicies []*egv1a1.ClientTrafficPolicy, + securityPolicies []*egv1a1.SecurityPolicy, xdsIR XdsIRMap, infraIR InfraIRMap) *TranslateResult { translateResult := &TranslateResult{ XdsIR: xdsIR, @@ -118,7 +119,7 @@ func newTranslateResult(gateways []*GatewayContext, } translateResult.ClientTrafficPolicies = append(translateResult.ClientTrafficPolicies, clientTrafficPolicies...) - + translateResult.SecurityPolicies = append(translateResult.SecurityPolicies, securityPolicies...) return translateResult } @@ -138,6 +139,9 @@ func (t *Translator) Translate(resources *Resources) *TranslateResult { // Process ClientTrafficPolicies clientTrafficPolicies := ProcessClientTrafficPolicies(resources.ClientTrafficPolicies, gateways, xdsIR) + // Process SecurityPolicies + securityPolicies := ProcessSecurityPolicies(resources.SecurityPolicies, gateways, xdsIR) + // Process all Addresses for all relevant Gateways. t.ProcessAddresses(gateways, xdsIR, infraIR, resources) @@ -159,7 +163,7 @@ func (t *Translator) Translate(resources *Resources) *TranslateResult { // Sort xdsIR based on the Gateway API spec sortXdsIRMap(xdsIR) - return newTranslateResult(gateways, httpRoutes, grpcRoutes, tlsRoutes, tcpRoutes, udpRoutes, clientTrafficPolicies, xdsIR, infraIR) + return newTranslateResult(gateways, httpRoutes, grpcRoutes, tlsRoutes, tcpRoutes, udpRoutes, clientTrafficPolicies, securityPolicies, xdsIR, infraIR) } // GetRelevantGateways returns GatewayContexts, containing a copy of the original diff --git a/internal/gatewayapi/zz_generated.deepcopy.go b/internal/gatewayapi/zz_generated.deepcopy.go index 9d09747b7110..3c4c2f368277 100644 --- a/internal/gatewayapi/zz_generated.deepcopy.go +++ b/internal/gatewayapi/zz_generated.deepcopy.go @@ -216,6 +216,17 @@ func (in *Resources) DeepCopyInto(out *Resources) { } } } + if in.SecurityPolicies != nil { + in, out := &in.SecurityPolicies, &out.SecurityPolicies + *out = make([]*apiv1alpha1.SecurityPolicy, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(apiv1alpha1.SecurityPolicy) + (*in).DeepCopyInto(*out) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resources. diff --git a/internal/message/types.go b/internal/message/types.go index 7b38a5afa6e2..b0423234bce5 100644 --- a/internal/message/types.go +++ b/internal/message/types.go @@ -79,11 +79,13 @@ func (s *GatewayAPIStatuses) Close() { // PolicyStatuses contains policy related resources statuses type PolicyStatuses struct { ClientTrafficPolicyStatuses watchable.Map[types.NamespacedName, *egv1a1.ClientTrafficPolicyStatus] + SecurityPolicyStatuses watchable.Map[types.NamespacedName, *egv1a1.SecurityPolicyStatus] EnvoyPatchPolicyStatuses watchable.Map[types.NamespacedName, *egv1a1.EnvoyPatchPolicyStatus] } func (p *PolicyStatuses) Close() { p.ClientTrafficPolicyStatuses.Close() + p.SecurityPolicyStatuses.Close() p.EnvoyPatchPolicyStatuses.Close() } diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index dc7f1b3e6d1d..7cf8bd247aae 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -306,6 +306,21 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques } + // Add all SecurityPolicies + securityPolicies := egv1a1.SecurityPolicyList{} + if err := r.client.List(ctx, &securityPolicies); err != nil { + return reconcile.Result{}, fmt.Errorf("error listing SecurityPolicies: %v", err) + } + + for _, policy := range securityPolicies.Items { + policy := policy + // Discard Status to reduce memory consumption in watchable + // It will be recomputed by the gateway-api layer + policy.Status = egv1a1.SecurityPolicyStatus{} + resourceTree.SecurityPolicies = append(resourceTree.SecurityPolicies, &policy) + + } + // For this particular Gateway, and all associated objects, check whether the // namespace exists. Add to the resourceTree. for ns := range resourceMap.allAssociatedNamespaces { diff --git a/internal/status/securitypolicy.go b/internal/status/securitypolicy.go new file mode 100644 index 000000000000..ee3cfebe53f6 --- /dev/null +++ b/internal/status/securitypolicy.go @@ -0,0 +1,20 @@ +// 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. + +package status + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" +) + +func SetSecurityPolicyCondition(c *egv1a1.SecurityPolicy, conditionType gwv1a2.PolicyConditionType, status metav1.ConditionStatus, reason gwv1a2.PolicyConditionReason, message string) { + cond := newCondition(string(conditionType), status, string(reason), message, time.Now(), c.Generation) + c.Status.Conditions = MergeConditions(c.Status.Conditions, cond) +} diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 73bff134936c..e37f73cb1bfe 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -22,6 +22,8 @@ API group. - [EnvoyPatchPolicyList](#envoypatchpolicylist) - [EnvoyProxy](#envoyproxy) - [RateLimitFilter](#ratelimitfilter) +- [SecurityPolicy](#securitypolicy) +- [SecurityPolicyList](#securitypolicylist) @@ -1389,6 +1391,55 @@ _Appears in:_ +#### SecurityPolicy + + + +SecurityPolicy allows the user to configure the security gesture of the connection between the downstream client and Envoy Proxy listener. + +_Appears in:_ +- [SecurityPolicyList](#securitypolicylist) + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `gateway.envoyproxy.io/v1alpha1` +| `kind` _string_ | `SecurityPolicy` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[SecurityPolicySpec](#securitypolicyspec)_ | Spec defines the desired state of SecurityPolicy. | + + +#### SecurityPolicyList + + + +SecurityPolicyList contains a list of SecurityPolicy resources. + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `gateway.envoyproxy.io/v1alpha1` +| `kind` _string_ | `SecurityPolicyList` +| `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` _[SecurityPolicy](#securitypolicy) array_ | | + + +#### SecurityPolicySpec + + + +SecurityPolicySpec defines the desired state of SecurityPolicy. + +_Appears in:_ +- [SecurityPolicy](#securitypolicy) + +| Field | Description | +| --- | --- | +| `targetRef` _[PolicyTargetReferenceWithSectionName](#policytargetreferencewithsectionname)_ | 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 for this Policy to have effect and be applied to the Gateway. TargetRef | + + + + #### ServiceType _Underlying type:_ `string`