diff --git a/api/v1alpha1/policy_helpers.go b/api/v1alpha1/policy_helpers.go index 32bc1fa7d8..e6dac99332 100644 --- a/api/v1alpha1/policy_helpers.go +++ b/api/v1alpha1/policy_helpers.go @@ -42,6 +42,7 @@ type PolicyTargetReferences struct { } // +kubebuilder:validation:XValidation:rule="has(self.group) ? self.group == 'gateway.networking.k8s.io' : true ", message="group must be gateway.networking.k8s.io" +// +kubebuilder:validation:XValidation:rule="has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions)", message="at least one of namespaces, matchLabels, or matchExpressions must be specified" type TargetSelector struct { // Group is the group that this selector targets. Defaults to gateway.networking.k8s.io // @@ -51,7 +52,18 @@ type TargetSelector struct { // Kind is the resource kind that this selector targets. Kind gwapiv1.Kind `json:"kind"` - // MatchLabels are the set of label selectors for identifying the targeted resource + // Namespaces determines which namespaces are considered for target selection. + // + // If unspecified, only targets in the same namespace as this policy are considered. + // + // When specified, the effective set of namespaces is always constrained to the + // namespaces watched by Envoy Gateway. + // + // +optional + Namespaces *TargetSelectorNamespaces `json:"namespaces,omitempty"` + + // MatchLabels are the set of label selectors for identifying the targeted resource. + // // +optional MatchLabels map[string]string `json:"matchLabels,omitempty"` @@ -62,6 +74,35 @@ type TargetSelector struct { MatchExpressions []metav1.LabelSelectorRequirement `json:"matchExpressions,omitempty"` } +type TargetNamespaceFrom string + +const ( + // TargetNamespaceFromSame limits target selection to the policy's namespace. + TargetNamespaceFromSame TargetNamespaceFrom = "Same" + // TargetNamespaceFromAll allows target selection from all watched namespaces. + TargetNamespaceFromAll TargetNamespaceFrom = "All" + // TargetNamespaceFromSelector allows target selection from watched namespaces matching the selector. + TargetNamespaceFromSelector TargetNamespaceFrom = "Selector" +) + +// TargetSelectorNamespaces determines which namespaces are considered for target selection. +// +kubebuilder:validation:XValidation:rule="self.from != 'Selector' || has(self.selector)", message="selector must be specified when from is Selector" +type TargetSelectorNamespaces struct { + // From indicates how namespaces are selected for this target selector. + // + // All means all namespaces watched by Envoy Gateway. + // Selector means namespaces watched by Envoy Gateway that match Selector. + // + // +kubebuilder:validation:Enum=Same;All;Selector + // +kubebuilder:default:=Same + From TargetNamespaceFrom `json:"from"` + + // Selector selects namespaces when From is set to Selector. + // + // +optional + Selector *metav1.LabelSelector `json:"selector,omitempty"` +} + func (p PolicyTargetReferences) GetTargetRefs() []gwapiv1.LocalPolicyTargetReferenceWithSectionName { if p.TargetRef != nil { return []gwapiv1.LocalPolicyTargetReferenceWithSectionName{*p.TargetRef} diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go index 27178d2545..7aee3bac0c 100644 --- a/api/v1alpha1/shared_types.go +++ b/api/v1alpha1/shared_types.go @@ -67,6 +67,10 @@ const ( // PolicyReasonDeprecatedField is used with the "Warning" condition when the policy // uses deprecated fields that should be migrated to newer alternatives. PolicyReasonDeprecatedField gwapiv1.PolicyConditionReason = "DeprecatedField" + + // PolicyReasonRefNotPermitted is used with the "Accepted" condition when the policy + // targets a cross-namespace object without a matching ReferenceGrant. + PolicyReasonRefNotPermitted gwapiv1.PolicyConditionReason = "RefNotPermitted" ) // GroupVersionKind unambiguously identifies a Kind. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 22be0adbc7..a775398884 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -8065,6 +8065,11 @@ func (in *TargetSelector) DeepCopyInto(out *TargetSelector) { *out = new(v1.Group) **out = **in } + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = new(TargetSelectorNamespaces) + (*in).DeepCopyInto(*out) + } if in.MatchLabels != nil { in, out := &in.MatchLabels, &out.MatchLabels *out = make(map[string]string, len(*in)) @@ -8091,6 +8096,26 @@ func (in *TargetSelector) DeepCopy() *TargetSelector { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TargetSelectorNamespaces) DeepCopyInto(out *TargetSelectorNamespaces) { + *out = *in + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetSelectorNamespaces. +func (in *TargetSelectorNamespaces) DeepCopy() *TargetSelectorNamespaces { + if in == nil { + return nil + } + out := new(TargetSelectorNamespaces) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Timeout) DeepCopyInto(out *Timeout) { *out = *in diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index 856f8d8522..6adb730f19 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -2691,8 +2691,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -2700,6 +2774,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array tcpKeepalive: description: |- diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml index c1f6cd7f0f..b861620191 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml @@ -1171,8 +1171,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -1180,6 +1254,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array tcpKeepalive: description: |- diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index 2f67685ec6..b48be763c0 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -1807,8 +1807,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -1816,6 +1890,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array wasm: description: |- diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_securitypolicies.yaml index 21f522c267..f69b035707 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -7091,8 +7091,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -7100,6 +7174,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array type: object x-kubernetes-validations: diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index aa12901fec..acbbbef588 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -2690,8 +2690,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -2699,6 +2773,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array tcpKeepalive: description: |- diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml index 853710646a..716c319ec7 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml @@ -1170,8 +1170,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -1179,6 +1253,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array tcpKeepalive: description: |- diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index 7138e01ad0..08cbc42685 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -1806,8 +1806,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -1815,6 +1889,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array wasm: description: |- diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml index 3d839207a6..cbabe3e7c1 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -7090,8 +7090,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -7099,6 +7173,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array type: object x-kubernetes-validations: diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index ef5b9086b7..7e8c7993ec 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -14,12 +14,14 @@ import ( "time" perr "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" egv1a1validation "github.com/envoyproxy/gateway/api/v1alpha1/validation" @@ -68,6 +70,8 @@ func BuildBTPRoutingTypeIndex( btps []*egv1a1.BackendTrafficPolicy, routes []client.Object, gateways []*GatewayContext, + referenceGrants []*gwapiv1b1.ReferenceGrant, + namespaceLookup func(string) *corev1.Namespace, ) *BTPRoutingTypeIndex { idx := &BTPRoutingTypeIndex{ routeRuleLevel: make(map[btpRoutingKey]*egv1a1.RoutingType), @@ -88,7 +92,18 @@ func BuildBTPRoutingTypeIndex( continue } - refs := getPolicyTargetRefs(btp.Spec.PolicyTargetReferences, allTargets, btp.Namespace) + refs := getPolicyTargetRefs( + btp.Spec.PolicyTargetReferences, + allTargets, + crossNamespaceFrom{ + group: egv1a1.GroupVersion.Group, + kind: "BackendTrafficPolicy", + namespace: btp.Namespace, + }, + referenceGrants, + btp.Namespace, + namespaceLookup, + ) for _, ref := range refs { kind := string(ref.Kind) key := btpRoutingKey{ @@ -259,12 +274,22 @@ func (t *Translator) ProcessBackendTrafficPolicies( // 4. Finally, the policies targeting Gateways // Build gateway policy maps, which are needed when processing the policies targeting xRoutes. - t.buildGatewayPolicyMap(backendTrafficPolicies, gateways, gatewayMap, gatewayPolicyMap) + t.buildGatewayPolicyMap(backendTrafficPolicies, gateways, gatewayMap, gatewayPolicyMap, resources.ReferenceGrants) // Process the policies targeting RouteRules for i, currPolicy := range backendTrafficPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) + routeMatches := getPolicySelectorTargetMatches(currPolicy.Spec.PolicyTargetReferences, routes, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "BackendTrafficPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "BackendTrafficPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + if len(routeMatches.Denied) > 0 { + policy, found := handledPolicies[policyName] + if !found { + policy = currPolicy + handledPolicies[policyName] = policy + res = append(res, policy) + } + setPolicyTargetRefNotPermittedStatus(&policy.Status, routeMatches.Denied, t.GatewayControllerName, policy.Generation) + } for _, currTarget := range targetRefs { // If the target is not a gateway, then it's an xRoute. If the section name is defined, then it's a route rule. if currTarget.Kind != resource.KindGateway && currTarget.SectionName != nil { @@ -284,7 +309,7 @@ func (t *Translator) ProcessBackendTrafficPolicies( // Process the policies targeting Routes for i, currPolicy := range backendTrafficPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "BackendTrafficPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) for _, currTarget := range targetRefs { // If the target is not a gateway, then it's an xRoute. If the section name is not defined, then it's a route. if currTarget.Kind != resource.KindGateway && currTarget.SectionName == nil { @@ -304,7 +329,17 @@ func (t *Translator) ProcessBackendTrafficPolicies( // Process the policies targeting Listeners for i, currPolicy := range backendTrafficPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) + gatewayMatches := getPolicySelectorTargetMatches(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "BackendTrafficPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "BackendTrafficPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + if len(gatewayMatches.Denied) > 0 { + policy, found := handledPolicies[policyName] + if !found { + policy = currPolicy + handledPolicies[policyName] = policy + res = append(res, policy) + } + setPolicyTargetRefNotPermittedStatus(&policy.Status, gatewayMatches.Denied, t.GatewayControllerName, policy.Generation) + } for _, currTarget := range targetRefs { // If the target is a gateway and the section name is defined, then it's a listener. if currTarget.Kind == resource.KindGateway && currTarget.SectionName != nil { @@ -314,8 +349,9 @@ func (t *Translator) ProcessBackendTrafficPolicies( handledPolicies[policyName] = policy res = append(res, policy) } + targetNamespace := namespaceForPolicyTargetRef(currTarget, currPolicy.Namespace, gatewayMatches.Allowed) t.processBackendTrafficPolicyForGateway(xdsIR, - gatewayMap, gatewayRouteMap, gatewayPolicyMerged, policy, currTarget) + gatewayMap, gatewayRouteMap, gatewayPolicyMerged, policy, currTarget, targetNamespace) } } } @@ -323,7 +359,8 @@ func (t *Translator) ProcessBackendTrafficPolicies( // Process the policies targeting Gateways for i, currPolicy := range backendTrafficPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) + gatewayMatches := getPolicySelectorTargetMatches(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "BackendTrafficPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "BackendTrafficPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) for _, currTarget := range targetRefs { // If the target is a gateway and the section name is not defined, then it's a gateway. if currTarget.Kind == resource.KindGateway && currTarget.SectionName == nil { @@ -333,8 +370,9 @@ func (t *Translator) ProcessBackendTrafficPolicies( handledPolicies[policyName] = policy res = append(res, policy) } + targetNamespace := namespaceForPolicyTargetRef(currTarget, currPolicy.Namespace, gatewayMatches.Allowed) t.processBackendTrafficPolicyForGateway(xdsIR, - gatewayMap, gatewayRouteMap, gatewayPolicyMerged, policy, currTarget) + gatewayMap, gatewayRouteMap, gatewayPolicyMerged, policy, currTarget, targetNamespace) } } } @@ -353,15 +391,17 @@ func (t *Translator) buildGatewayPolicyMap( gateways []*GatewayContext, gatewayMap map[types.NamespacedName]*policyGatewayTargetContext, gatewayPolicyMap map[NamespacedNameWithSection]*egv1a1.BackendTrafficPolicy, + referenceGrants []*gwapiv1b1.ReferenceGrant, ) { for _, currPolicy := range backendTrafficPolicies { - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) + gatewayMatches := getPolicySelectorTargetMatches(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "BackendTrafficPolicy", namespace: currPolicy.Namespace}, referenceGrants, currPolicy.Namespace, t.GetNamespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "BackendTrafficPolicy", namespace: currPolicy.Namespace}, referenceGrants, currPolicy.Namespace, t.GetNamespace) for _, currTarget := range targetRefs { if currTarget.Kind == resource.KindGateway { // Check if the gateway exists key := types.NamespacedName{ Name: string(currTarget.Name), - Namespace: currPolicy.Namespace, + Namespace: namespaceForPolicyTargetRef(currTarget, currPolicy.Namespace, gatewayMatches.Allowed), } gateway, ok := gatewayMap[key] if !ok { @@ -399,14 +439,14 @@ func (t *Translator) processBackendTrafficPolicyForRoute( gatewayPolicyMergedMap *GatewayPolicyRouteMap, gatewayPolicyMap map[NamespacedNameWithSection]*egv1a1.BackendTrafficPolicy, policy *egv1a1.BackendTrafficPolicy, - currTarget gwapiv1.LocalPolicyTargetReferenceWithSectionName, + currTarget policyTargetReferenceWithSectionName, ) { var ( targetedRoute RouteContext resolveErr *status.PolicyResolveError ) - targetedRoute, resolveErr = resolveBackendTrafficPolicyRouteTargetRef(policy, currTarget, routeMap) + targetedRoute, resolveErr = resolveBackendTrafficPolicyRouteTargetRef(currTarget, routeMap) // Skip if the route is not found // It's not necessarily an error because the BackendTrafficPolicy may be // reconciled by multiple controllers. And the other controller may @@ -570,7 +610,7 @@ func (t *Translator) processBackendTrafficPolicyForRoute( key := policyTargetRouteKey{ Kind: string(currTarget.Kind), Name: string(currTarget.Name), - Namespace: policy.Namespace, + Namespace: string(currTarget.Namespace), } overriddenTargetsMessage := getOverriddenTargetsMessageForRoute(routeMap[key], currTarget.SectionName) if overriddenTargetsMessage != "" { @@ -592,7 +632,8 @@ func (t *Translator) processBackendTrafficPolicyForGateway( gatewayRouteMap *GatewayPolicyRouteMap, gatewayPolicyMergedMap *GatewayPolicyRouteMap, policy *egv1a1.BackendTrafficPolicy, - currTarget gwapiv1.LocalPolicyTargetReferenceWithSectionName, + currTarget policyTargetReferenceWithSectionName, + targetNamespace string, ) { var ( targetedGateway *GatewayContext @@ -600,7 +641,7 @@ func (t *Translator) processBackendTrafficPolicyForGateway( ) // Negative statuses have already been assigned so it's safe to skip - targetedGateway, resolveErr = resolveBackendTrafficPolicyGatewayTargetRef(policy, currTarget, gatewayMap) + targetedGateway, resolveErr = resolveBackendTrafficPolicyGatewayTargetRef(currTarget, targetNamespace, gatewayMap) if targetedGateway == nil { return } @@ -666,14 +707,14 @@ func (t *Translator) processBackendTrafficPolicyForGateway( } func resolveBackendTrafficPolicyGatewayTargetRef( - policy *egv1a1.BackendTrafficPolicy, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, + targetNamespace string, gateways map[types.NamespacedName]*policyGatewayTargetContext, ) (*GatewayContext, *status.PolicyResolveError) { // Check if the gateway exists key := types.NamespacedName{ Name: string(target.Name), - Namespace: policy.Namespace, + Namespace: targetNamespace, } gateway, ok := gateways[key] @@ -730,15 +771,14 @@ func resolveBackendTrafficPolicyGatewayTargetRef( } func resolveBackendTrafficPolicyRouteTargetRef( - policy *egv1a1.BackendTrafficPolicy, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, routes map[policyTargetRouteKey]*policyRouteTargetContext, ) (RouteContext, *status.PolicyResolveError) { // Check if the route exists key := policyTargetRouteKey{ Kind: string(target.Kind), Name: string(target.Name), - Namespace: policy.Namespace, + Namespace: string(target.Namespace), } route, ok := routes[key] @@ -791,7 +831,7 @@ func resolveBackendTrafficPolicyRouteTargetRef( func (t *Translator) translateBackendTrafficPolicyForRoute( policy *egv1a1.BackendTrafficPolicy, route RouteContext, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, xdsIR resource.XdsIRMap, policyTargetGatewayNN *types.NamespacedName, policyTargetListener *gwapiv1.SectionName, @@ -817,7 +857,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute( func (t *Translator) translateBackendTrafficPolicyForRouteWithMerge( policy, parentPolicy *egv1a1.BackendTrafficPolicy, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, policyTargetGatewayNN types.NamespacedName, policyTargetListener *gwapiv1.SectionName, route RouteContext, xdsIR resource.XdsIRMap, ) error { @@ -878,7 +918,7 @@ func (t *Translator) translateBackendTrafficPolicyForRouteWithMerge( func (t *Translator) applyTrafficFeatureToRoute(route RouteContext, tf *ir.TrafficFeatures, errs error, policy *egv1a1.BackendTrafficPolicy, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, x *ir.Xds, policyTargetListener *gwapiv1.SectionName, ) { @@ -1171,7 +1211,7 @@ func buildBackendMetrics(metrics *egv1a1.BackendMetrics) *ir.BackendMetrics { } func (t *Translator) translateBackendTrafficPolicyForGateway( - policy *egv1a1.BackendTrafficPolicy, target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + policy *egv1a1.BackendTrafficPolicy, target policyTargetReferenceWithSectionName, gateway *GatewayContext, xdsIR resource.XdsIRMap, ) error { tf, errs := t.buildTrafficFeatures(policy) diff --git a/internal/gatewayapi/backendtrafficpolicy_test.go b/internal/gatewayapi/backendtrafficpolicy_test.go index a1de4ab4e8..f800e4f977 100644 --- a/internal/gatewayapi/backendtrafficpolicy_test.go +++ b/internal/gatewayapi/backendtrafficpolicy_test.go @@ -1743,7 +1743,7 @@ func TestBTPRoutingTypeIndex(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - idx := BuildBTPRoutingTypeIndex(tt.btps, tt.routes, tt.gateways) + idx := BuildBTPRoutingTypeIndex(tt.btps, tt.routes, tt.gateways, nil, nil) got := idx.LookupBTPRoutingType(tt.routeKind, tt.routeNN, tt.gatewayNN, tt.listenerName, tt.routeRuleName) require.Equal(t, tt.expected, got) }) diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go index 737acd0fc9..952cd169d9 100644 --- a/internal/gatewayapi/clienttrafficpolicy.go +++ b/internal/gatewayapi/clienttrafficpolicy.go @@ -32,7 +32,7 @@ const ( AllSections = "/" ) -func hasSectionName(target *gwapiv1.LocalPolicyTargetReferenceWithSectionName) bool { +func hasSectionName(target *policyTargetReferenceWithSectionName) bool { return target.SectionName != nil } @@ -92,7 +92,14 @@ func (t *Translator) ProcessClientTrafficPolicies( // so there's no need to try to match targets with selectors targetRefs := currPolicy.Spec.GetTargetRefs() for _, currTarget := range targetRefs { - if hasSectionName(&currTarget) { + targetRef := policyTargetReferenceWithSectionName{ + Group: currTarget.Group, + Kind: currTarget.Kind, + Name: currTarget.Name, + Namespace: gwapiv1.Namespace(currPolicy.Namespace), + SectionName: currTarget.SectionName, + } + if hasSectionName(&targetRef) { policy, found := handledPolicies[policyName] if !found { policy = policyCopies[i] @@ -100,7 +107,7 @@ func (t *Translator) ProcessClientTrafficPolicies( res = append(res, policy) } - gateway, resolveErr := resolveClientTrafficPolicyTargetRef(policy, &currTarget, gatewayMap) + gateway, resolveErr := resolveClientTrafficPolicyTargetRef(&targetRef, gatewayMap) // Negative statuses have already been assigned so its safe to skip if gateway == nil { @@ -197,7 +204,39 @@ func (t *Translator) ProcessClientTrafficPolicies( // Policy with no section set (targeting all sections) for i, currPolicy := range clientTrafficPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) + matches := getPolicySelectorTargetMatches( + currPolicy.Spec.PolicyTargetReferences, + gateways, + crossNamespaceFrom{ + group: egv1a1.GroupVersion.Group, + kind: "ClientTrafficPolicy", + namespace: currPolicy.Namespace, + }, + resources.ReferenceGrants, + currPolicy.Namespace, + t.GetNamespace, + ) + targetRefs := getPolicyTargetRefs( + currPolicy.Spec.PolicyTargetReferences, + gateways, + crossNamespaceFrom{ + group: egv1a1.GroupVersion.Group, + kind: "ClientTrafficPolicy", + namespace: currPolicy.Namespace, + }, + resources.ReferenceGrants, + currPolicy.Namespace, + t.GetNamespace, + ) + if len(matches.Denied) > 0 { + policy, found := handledPolicies[policyName] + if !found { + policy = currPolicy + res = append(res, policy) + handledPolicies[policyName] = policy + } + setPolicyTargetRefNotPermittedStatus(&policy.Status, matches.Denied, t.GatewayControllerName, policy.Generation) + } for _, currTarget := range targetRefs { if !hasSectionName(&currTarget) { @@ -208,7 +247,7 @@ func (t *Translator) ProcessClientTrafficPolicies( handledPolicies[policyName] = policy } - gateway, resolveErr := resolveClientTrafficPolicyTargetRef(policy, &currTarget, gatewayMap) + gateway, resolveErr := resolveClientTrafficPolicyTargetRef(&currTarget, gatewayMap) // Negative statuses have already been assigned so its safe to skip if gateway == nil { @@ -339,14 +378,13 @@ func (t *Translator) ProcessClientTrafficPolicies( } func resolveClientTrafficPolicyTargetRef( - policy *egv1a1.ClientTrafficPolicy, - targetRef *gwapiv1.LocalPolicyTargetReferenceWithSectionName, + targetRef *policyTargetReferenceWithSectionName, gateways map[types.NamespacedName]*policyGatewayTargetContext, ) (*GatewayContext, *status.PolicyResolveError) { // Check if the gateway exists key := types.NamespacedName{ Name: string(targetRef.Name), - Namespace: policy.Namespace, + Namespace: string(targetRef.Namespace), } gateway, ok := gateways[key] diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index 05ee608fb3..3ed286cd3d 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -116,7 +116,17 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( // Process the policies targeting RouteRules for i, currPolicy := range envoyExtensionPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) + routeMatches := getPolicySelectorTargetMatches(currPolicy.Spec.PolicyTargetReferences, routes, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "EnvoyExtensionPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "EnvoyExtensionPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + if len(routeMatches.Denied) > 0 { + policy, found := handledPolicies[policyName] + if !found { + policy = currPolicy + res = append(res, policy) + handledPolicies[policyName] = policy + } + setPolicyTargetRefNotPermittedStatus(&policy.Status, routeMatches.Denied, t.GatewayControllerName, policy.Generation) + } for _, currTarget := range targetRefs { // If the target is not a gateway, then it's an xRoute. If the section name is defined, then it's a route rule. if currTarget.Kind != resource.KindGateway && currTarget.SectionName != nil { @@ -136,7 +146,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( // Process the policies targeting xRoutes for i, currPolicy := range envoyExtensionPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "EnvoyExtensionPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) for _, currTarget := range targetRefs { // If the target is not a gateway, then it's an xRoute. If the section name is not defined, then it's a route. if currTarget.Kind != resource.KindGateway && currTarget.SectionName == nil { @@ -156,7 +166,17 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( // Process the policies targeting Listeners for i, currPolicy := range envoyExtensionPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) + gatewayMatches := getPolicySelectorTargetMatches(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "EnvoyExtensionPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "EnvoyExtensionPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + if len(gatewayMatches.Denied) > 0 { + policy, found := handledPolicies[policyName] + if !found { + policy = currPolicy + res = append(res, policy) + handledPolicies[policyName] = policy + } + setPolicyTargetRefNotPermittedStatus(&policy.Status, gatewayMatches.Denied, t.GatewayControllerName, policy.Generation) + } for _, currTarget := range targetRefs { // If the target is a gateway and the section name is defined, then it's a listener. if currTarget.Kind == resource.KindGateway && currTarget.SectionName != nil { @@ -176,7 +196,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( // Process the policies targeting Gateways for i, currPolicy := range envoyExtensionPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "EnvoyExtensionPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) for _, currTarget := range targetRefs { // If the target is a gateway and the section name is not defined, then it's a gateway. if currTarget.Kind == resource.KindGateway && currTarget.SectionName == nil { @@ -208,7 +228,7 @@ func (t *Translator) processEnvoyExtensionPolicyForRoute( routeMap map[policyTargetRouteKey]*policyRouteTargetContext, gatewayRouteMap map[string]map[string]sets.Set[string], policy *egv1a1.EnvoyExtensionPolicy, - currTarget gwapiv1.LocalPolicyTargetReferenceWithSectionName, + currTarget policyTargetReferenceWithSectionName, ) { var ( targetedRoute RouteContext @@ -216,7 +236,7 @@ func (t *Translator) processEnvoyExtensionPolicyForRoute( resolveErr *status.PolicyResolveError ) - targetedRoute, resolveErr = resolveEnvoyExtensionPolicyRouteTargetRef(policy, currTarget, routeMap) + targetedRoute, resolveErr = resolveEnvoyExtensionPolicyRouteTargetRef(currTarget, routeMap) // Skip if the route is not found // It's not necessarily an error because the EnvoyExtensionPolicy may be // reconciled by multiple controllers. And the other controller may @@ -293,7 +313,7 @@ func (t *Translator) processEnvoyExtensionPolicyForRoute( key := policyTargetRouteKey{ Kind: string(currTarget.Kind), Name: string(currTarget.Name), - Namespace: policy.Namespace, + Namespace: string(currTarget.Namespace), } overriddenTargetsMessage := getOverriddenTargetsMessageForRoute(routeMap[key], currTarget.SectionName) if overriddenTargetsMessage != "" { @@ -315,14 +335,14 @@ func (t *Translator) processEnvoyExtensionPolicyForGateway( gatewayMap map[types.NamespacedName]*policyGatewayTargetContext, gatewayRouteMap map[string]map[string]sets.Set[string], policy *egv1a1.EnvoyExtensionPolicy, - currTarget gwapiv1.LocalPolicyTargetReferenceWithSectionName, + currTarget policyTargetReferenceWithSectionName, ) { var ( targetedGateway *GatewayContext resolveErr *status.PolicyResolveError ) - targetedGateway, resolveErr = resolveEnvoyExtensionPolicyGatewayTargetRef(policy, currTarget, gatewayMap) + targetedGateway, resolveErr = resolveEnvoyExtensionPolicyGatewayTargetRef(currTarget, gatewayMap) // Skip if the gateway is not found // It's not necessarily an error because the EnvoyExtensionPolicy may be // reconciled by multiple controllers. And the other controller may @@ -382,14 +402,13 @@ func (t *Translator) processEnvoyExtensionPolicyForGateway( } func resolveEnvoyExtensionPolicyGatewayTargetRef( - policy *egv1a1.EnvoyExtensionPolicy, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, gateways map[types.NamespacedName]*policyGatewayTargetContext, ) (*GatewayContext, *status.PolicyResolveError) { // Check if the gateway exists key := types.NamespacedName{ Name: string(target.Name), - Namespace: policy.Namespace, + Namespace: string(target.Namespace), } gateway, ok := gateways[key] @@ -444,15 +463,14 @@ func resolveEnvoyExtensionPolicyGatewayTargetRef( } func resolveEnvoyExtensionPolicyRouteTargetRef( - policy *egv1a1.EnvoyExtensionPolicy, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, routes map[policyTargetRouteKey]*policyRouteTargetContext, ) (RouteContext, *status.PolicyResolveError) { // Check if the route exists key := policyTargetRouteKey{ Kind: string(target.Kind), Name: string(target.Name), - Namespace: policy.Namespace, + Namespace: string(target.Namespace), } route, ok := routes[key] @@ -505,7 +523,7 @@ func resolveEnvoyExtensionPolicyRouteTargetRef( func (t *Translator) translateEnvoyExtensionPolicyForRoute( policy *egv1a1.EnvoyExtensionPolicy, route RouteContext, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, xdsIR resource.XdsIRMap, resources *resource.Resources, ) error { @@ -619,7 +637,7 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute( func (t *Translator) translateEnvoyExtensionPolicyForGateway( policy *egv1a1.EnvoyExtensionPolicy, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, gateway *GatewayContext, xdsIR resource.XdsIRMap, resources *resource.Resources, diff --git a/internal/gatewayapi/extensionserverpolicy.go b/internal/gatewayapi/extensionserverpolicy.go index 12530c6f6f..40a0e94674 100644 --- a/internal/gatewayapi/extensionserverpolicy.go +++ b/internal/gatewayapi/extensionserverpolicy.go @@ -12,10 +12,12 @@ import ( "maps" "strings" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/gatewayapi/resource" @@ -26,6 +28,7 @@ import ( func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unstructured, gateways []*GatewayContext, + referenceGrants []*gwapiv1b1.ReferenceGrant, xdsIR resource.XdsIRMap, ) ([]unstructured.Unstructured, error) { res := []unstructured.Unstructured{} @@ -47,7 +50,8 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst policy := policyCopies[i] var policyStatus gwapiv1.PolicyStatus accepted := false - targetRefs, err := extractTargetRefs(&policy, gateways) + + targetRefs, err := extractTargetRefs(&policy, gateways, referenceGrants, t.GetNamespace) if err != nil { errs = errors.Join(errs, fmt.Errorf("error finding targetRefs for policy %s: %w", policy.GetName(), err)) continue @@ -92,7 +96,12 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst return res, errs } -func extractTargetRefs(policy *unstructured.Unstructured, gateways []*GatewayContext) ([]gwapiv1.LocalPolicyTargetReferenceWithSectionName, error) { +func extractTargetRefs( + policy *unstructured.Unstructured, + gateways []*GatewayContext, + referenceGrants []*gwapiv1b1.ReferenceGrant, + namespaceLookup func(string) *corev1.Namespace, +) ([]policyTargetReferenceWithSectionName, error) { spec, found := policy.Object["spec"].(map[string]any) if !found { return nil, fmt.Errorf("no targets found for the policy") @@ -105,14 +114,25 @@ func extractTargetRefs(policy *unstructured.Unstructured, gateways []*GatewayCon if err := json.Unmarshal(specAsJSON, &targetRefs); err != nil { return nil, fmt.Errorf("no targets found for the policy") } - ret := getPolicyTargetRefs(targetRefs, gateways, policy.GetNamespace()) + ret := getPolicyTargetRefs( + targetRefs, + gateways, + crossNamespaceFrom{ + group: policy.GroupVersionKind().Group, + kind: policy.GroupVersionKind().Kind, + namespace: policy.GetNamespace(), + }, + referenceGrants, + policy.GetNamespace(), + namespaceLookup, + ) if len(ret) == 0 { return nil, fmt.Errorf("no targets found for the policy") } return ret, nil } -func resolveExtServerPolicyGatewayTargetRef(policy *unstructured.Unstructured, target gwapiv1.LocalPolicyTargetReferenceWithSectionName, gateways map[types.NamespacedName]*policyGatewayTargetContext) *GatewayContext { +func resolveExtServerPolicyGatewayTargetRef(policy *unstructured.Unstructured, target policyTargetReferenceWithSectionName, gateways map[types.NamespacedName]*policyGatewayTargetContext) *GatewayContext { // Check if the gateway exists key := types.NamespacedName{ Name: string(target.Name), @@ -154,7 +174,7 @@ func ExtServerPolicyStatusAsPolicyStatus(policy *unstructured.Unstructured) gwap func (t *Translator) translateExtServerPolicyForGateway( policy *unstructured.Unstructured, gateway *GatewayContext, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, xdsIR resource.XdsIRMap, ) bool { irKey := t.getIRKey(gateway.Gateway) diff --git a/internal/gatewayapi/extensionserverpolicy_test.go b/internal/gatewayapi/extensionserverpolicy_test.go index 751a76af99..8dc8b64098 100644 --- a/internal/gatewayapi/extensionserverpolicy_test.go +++ b/internal/gatewayapi/extensionserverpolicy_test.go @@ -17,7 +17,7 @@ func TestExtractTargetRefs(t *testing.T) { tests := []struct { desc string specInput map[string]any - output []gwapiv1.LocalPolicyTargetReferenceWithSectionName + output []policyTargetReferenceWithSectionName expectedError string }{ { @@ -61,13 +61,11 @@ func TestExtractTargetRefs(t *testing.T) { "name": "name", }, }, - output: []gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + output: []policyTargetReferenceWithSectionName{ { - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: "some.group", - Kind: "SomeKind", - Name: "name", - }, + Group: "some.group", + Kind: "SomeKind", + Name: "name", }, }, }, @@ -87,20 +85,16 @@ func TestExtractTargetRefs(t *testing.T) { }, }, }, - output: []gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + output: []policyTargetReferenceWithSectionName{ { - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: "some.group", - Kind: "SomeKind2", - Name: "othername", - }, + Group: "some.group", + Kind: "SomeKind2", + Name: "othername", }, { - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: "some.group", - Kind: "SomeKind", - Name: "name", - }, + Group: "some.group", + Kind: "SomeKind", + Name: "name", }, }, }, @@ -112,7 +106,7 @@ func TestExtractTargetRefs(t *testing.T) { Object: map[string]any{}, } policy.Object["spec"] = currTest.specInput - targets, err := extractTargetRefs(policy, []*GatewayContext{}) + targets, err := extractTargetRefs(policy, []*GatewayContext{}, nil, nil) if currTest.expectedError != "" { require.EqualError(t, err, currTest.expectedError) diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index ea9ad9b5be..249b60509d 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -23,9 +23,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/gatewayapi/resource" + "github.com/envoyproxy/gateway/internal/gatewayapi/status" "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/utils" ) @@ -727,10 +729,54 @@ func irConfigName(policy client.Object) string { } type targetRefWithTimestamp struct { - gwapiv1.LocalPolicyTargetReferenceWithSectionName + policyTargetReferenceWithSectionName CreationTimestamp metav1.Time } +// policyTargetReferenceWithSectionName extends the Gateway API's LocalPolicyTargetReference to include a Namespace field. +// This is necessary because policies may reference targets in other namespaces. +type policyTargetReferenceWithSectionName struct { + // Group is the group of the target resource. + // +required + Group gwapiv1.Group `json:"group"` + + // Kind is kind of the target resource. + // +required + Kind gwapiv1.Kind `json:"kind"` + + // Name is the name of the target resource. + // +required + Name gwapiv1.ObjectName `json:"name"` + + // Namespace is the namespace of the target resource. When unspecified, it is assumed to be in the same namespace as the policy. + Namespace gwapiv1.Namespace `json:"namespace"` + + // SectionName is the name of a section within the target resource. When + // unspecified, this targetRef targets the entire resource. In the following + // resources, SectionName is interpreted as the following: + // + // * Gateway: Listener name + // * HTTPRoute: HTTPRouteRule name + // * Service: Port name + // + // If a SectionName is specified, but does not exist on the targeted object, + // the Policy must fail to attach, and the policy implementation should record + // a `ResolvedRefs` or similar Condition in the Policy's status. + // + // +optional + SectionName *gwapiv1.SectionName `json:"sectionName,omitempty"` +} + +type policySelectorTargetMatch[T client.Object] struct { + Object T + Ref policyTargetReferenceWithSectionName +} + +type policySelectorTargetMatches[T client.Object] struct { + Allowed []policySelectorTargetMatch[T] + Denied []policySelectorTargetMatch[T] +} + func selectorFromTargetSelector(selector egv1a1.TargetSelector) labels.Selector { l, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchLabels: selector.MatchLabels, @@ -743,8 +789,110 @@ func selectorFromTargetSelector(selector egv1a1.TargetSelector) labels.Selector return l } -func getPolicyTargetRefs[T client.Object](policy egv1a1.PolicyTargetReferences, potentialTargets []T, policyNamespace string) []gwapiv1.LocalPolicyTargetReferenceWithSectionName { - dedup := sets.New[targetRefWithTimestamp]() +func targetNamespaceLabelSelector(namespaces *egv1a1.TargetSelectorNamespaces) labels.Selector { + if namespaces == nil || namespaces.Selector == nil { + return labels.Nothing() + } + + selector, err := metav1.LabelSelectorAsSelector(namespaces.Selector) + if err != nil { + return labels.Nothing() + } + + return selector +} + +func targetSelectorNamespacesMatch( + namespaces *egv1a1.TargetSelectorNamespaces, + policyNamespace, + targetNamespace string, + targetNamespaceLabels map[string]string, +) bool { + if namespaces == nil { + return targetNamespace == policyNamespace + } + + switch namespaces.From { + case "", egv1a1.TargetNamespaceFromSame: + return targetNamespace == policyNamespace + case egv1a1.TargetNamespaceFromAll: + return true + case egv1a1.TargetNamespaceFromSelector: + if targetNamespaceLabels == nil { + return false + } + return targetNamespaceLabelSelector(namespaces).Matches(labels.Set(targetNamespaceLabels)) + default: + return false + } +} + +func targetNamespaceMatches( + selector egv1a1.TargetSelector, + policyNamespace, + targetNamespace string, + namespaceLookup func(string) *corev1.Namespace, +) bool { + var targetNamespaceLabels map[string]string + if namespaceLookup != nil { + if ns := namespaceLookup(targetNamespace); ns != nil { + targetNamespaceLabels = ns.GetLabels() + } + } + + return targetSelectorNamespacesMatch(selector.Namespaces, policyNamespace, targetNamespace, targetNamespaceLabels) +} + +func isCrossNamespacePolicyTargetRefAllowed( + from crossNamespaceFrom, + to crossNamespaceTo, + referenceGrants []*gwapiv1b1.ReferenceGrant, +) bool { + if from.namespace == to.namespace { + return true + } + + for _, referenceGrant := range referenceGrants { + if referenceGrant.Namespace != to.namespace { + continue + } + + var fromAllowed bool + for _, refGrantFrom := range referenceGrant.Spec.From { + if string(refGrantFrom.Namespace) == from.namespace && + string(refGrantFrom.Group) == from.group && + string(refGrantFrom.Kind) == from.kind { + fromAllowed = true + break + } + } + if !fromAllowed { + continue + } + + for _, refGrantTo := range referenceGrant.Spec.To { + if string(refGrantTo.Group) == to.group && + string(refGrantTo.Kind) == to.kind && + (refGrantTo.Name == nil || *refGrantTo.Name == "" || string(*refGrantTo.Name) == to.name) { + return true + } + } + } + + return false +} + +func getPolicySelectorTargetMatches[T client.Object]( + policy egv1a1.PolicyTargetReferences, + potentialTargets []T, + from crossNamespaceFrom, + referenceGrants []*gwapiv1b1.ReferenceGrant, + policyNamespace string, + namespaceLookup func(string) *corev1.Namespace, +) policySelectorTargetMatches[T] { + allowedDedup := sets.New[targetRefWithTimestamp]() + deniedDedup := sets.New[policyTargetReferenceWithSectionName]() + matches := policySelectorTargetMatches[T]{} for _, currSelector := range policy.TargetSelectors { labelSelector := selectorFromTargetSelector(currSelector) for _, obj := range potentialTargets { @@ -754,32 +902,82 @@ func getPolicyTargetRefs[T client.Object](policy egv1a1.PolicyTargetReferences, continue } - // Skip objects not in the same namespace as the policy - if obj.GetNamespace() != policyNamespace { + if !targetNamespaceMatches(currSelector, policyNamespace, obj.GetNamespace(), namespaceLookup) { continue } - if labelSelector.Matches(labels.Set(obj.GetLabels())) { - dedup.Insert(targetRefWithTimestamp{ - CreationTimestamp: obj.GetCreationTimestamp(), - LocalPolicyTargetReferenceWithSectionName: gwapiv1.LocalPolicyTargetReferenceWithSectionName{ - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: gwapiv1.Group(gvk.Group), - Kind: gwapiv1.Kind(gvk.Kind), - Name: gwapiv1.ObjectName(obj.GetName()), - }, - }, + ref := policyTargetReferenceWithSectionName{ + Group: gwapiv1.Group(gvk.Group), + Kind: gwapiv1.Kind(gvk.Kind), + Name: gwapiv1.ObjectName(obj.GetName()), + Namespace: gwapiv1.Namespace(obj.GetNamespace()), + } + + if !labelSelector.Matches(labels.Set(obj.GetLabels())) { + continue + } + + if !isCrossNamespacePolicyTargetRefAllowed( + from, + crossNamespaceTo{ + group: gvk.Group, + kind: gvk.Kind, + namespace: obj.GetNamespace(), + name: obj.GetName(), + }, + referenceGrants, + ) { + if deniedDedup.Has(ref) { + continue + } + deniedDedup.Insert(ref) + matches.Denied = append(matches.Denied, policySelectorTargetMatch[T]{ + Object: obj, + Ref: ref, }) + continue } + + targetRef := targetRefWithTimestamp{ + CreationTimestamp: obj.GetCreationTimestamp(), + policyTargetReferenceWithSectionName: ref, + } + if allowedDedup.Has(targetRef) { + continue + } + allowedDedup.Insert(targetRef) + matches.Allowed = append(matches.Allowed, policySelectorTargetMatch[T]{ + Object: obj, + Ref: ref, + }) } } - selectorsList := dedup.UnsortedList() + + return matches +} + +func getPolicyTargetRefs[T client.Object]( + policy egv1a1.PolicyTargetReferences, + potentialTargets []T, + from crossNamespaceFrom, + referenceGrants []*gwapiv1b1.ReferenceGrant, + policyNamespace string, + namespaceLookup func(string) *corev1.Namespace, +) []policyTargetReferenceWithSectionName { + matches := getPolicySelectorTargetMatches(policy, potentialTargets, from, referenceGrants, policyNamespace, namespaceLookup) + selectorsList := make([]targetRefWithTimestamp, 0, len(matches.Allowed)) + for _, match := range matches.Allowed { + selectorsList = append(selectorsList, targetRefWithTimestamp{ + CreationTimestamp: match.Object.GetCreationTimestamp(), + policyTargetReferenceWithSectionName: match.Ref, + }) + } slices.SortFunc(selectorsList, func(i, j targetRefWithTimestamp) int { return i.CreationTimestamp.Compare(j.CreationTimestamp.Time) }) - ret := make([]gwapiv1.LocalPolicyTargetReferenceWithSectionName, len(selectorsList)) + ret := make([]policyTargetReferenceWithSectionName, len(selectorsList)) for i, v := range selectorsList { - ret[i] = v.LocalPolicyTargetReferenceWithSectionName + ret[i] = v.policyTargetReferenceWithSectionName } // Plain targetRefs in the policy don't have an associated creation timestamp, but can still refer // to targets that were already found via the selectors. Only add them to the returned list if @@ -791,14 +989,80 @@ func getPolicyTargetRefs[T client.Object](policy egv1a1.PolicyTargetReferences, // This can happen when the targetRef structure is read from extension server policies continue } - if !fastLookup.Has(v) { - ret = append(ret, v) + targetRef := policyTargetReferenceWithSectionName{ + Group: v.Group, + Kind: v.Kind, + Name: v.Name, + Namespace: gwapiv1.Namespace(policyNamespace), + SectionName: v.SectionName, + } + if !fastLookup.Has(targetRef) { + ret = append(ret, targetRef) } } return ret } +func setPolicyTargetRefNotPermittedStatus[T client.Object]( + policyStatus *gwapiv1.PolicyStatus, + denied []policySelectorTargetMatch[T], + controllerName string, + generation int64, +) { + for _, deniedMatch := range denied { + msg := fmt.Sprintf( + "Target %s %s/%s is not permitted by any ReferenceGrant.", + deniedMatch.Object.GetObjectKind().GroupVersionKind().Kind, + deniedMatch.Object.GetNamespace(), + deniedMatch.Object.GetName(), + ) + + switch obj := any(deniedMatch.Object).(type) { + case *GatewayContext: + ancestorRef := getAncestorRefForPolicy(utils.NamespacedName(obj), nil) + status.SetResolveErrorForPolicyAncestor(policyStatus, &ancestorRef, controllerName, generation, &status.PolicyResolveError{ + Reason: egv1a1.PolicyReasonRefNotPermitted, + Message: msg, + }) + case RouteContext: + parentRefs := GetManagedParentReferences(obj) + ancestorRefs := make([]*gwapiv1.ParentReference, 0, len(parentRefs)) + for _, p := range parentRefs { + if p.Kind != nil && *p.Kind != resource.KindGateway { + continue + } + namespace := obj.GetNamespace() + if p.Namespace != nil { + namespace = string(*p.Namespace) + } + ancestorRef := getAncestorRefForPolicy(types.NamespacedName{ + Name: string(p.Name), + Namespace: namespace, + }, p.SectionName) + ancestorRefs = append(ancestorRefs, &ancestorRef) + } + status.SetResolveErrorForPolicyAncestors(policyStatus, ancestorRefs, controllerName, generation, &status.PolicyResolveError{ + Reason: egv1a1.PolicyReasonRefNotPermitted, + Message: msg, + }) + } + } +} + +func namespaceForPolicyTargetRef[T client.Object]( + target policyTargetReferenceWithSectionName, + defaultNamespace string, + matches []policySelectorTargetMatch[T], +) string { + for _, match := range matches { + if match.Ref == target { + return match.Object.GetNamespace() + } + } + return defaultNamespace +} + // Sets *target to value if and only if *target is nil func setIfNil[T any](target **T, value *T) { if *target == nil { diff --git a/internal/gatewayapi/helpers_test.go b/internal/gatewayapi/helpers_test.go index 864d65f931..8f78d0ae12 100644 --- a/internal/gatewayapi/helpers_test.go +++ b/internal/gatewayapi/helpers_test.go @@ -21,6 +21,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/ir" @@ -202,10 +203,12 @@ func TestValidateHTTPFilterRef(t *testing.T) { func TestGetPolicyTargetRefs(t *testing.T) { testCases := []struct { - name string - policy egv1a1.PolicyTargetReferences - targets []*unstructured.Unstructured - results []gwapiv1.LocalPolicyTargetReferenceWithSectionName + name string + policy egv1a1.PolicyTargetReferences + targets []*unstructured.Unstructured + namespaces []*corev1.Namespace + grants []*gwapiv1b1.ReferenceGrant + results []policyTargetReferenceWithSectionName }{ { name: "simple", @@ -261,13 +264,12 @@ func TestGetPolicyTargetRefs(t *testing.T) { }, }, }, - results: []gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + results: []policyTargetReferenceWithSectionName{ { - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: "gateway.networking.k8s.io", - Kind: "Gateway", - Name: "second", - }, + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "second", + Namespace: "default", }, }, }, @@ -330,20 +332,18 @@ func TestGetPolicyTargetRefs(t *testing.T) { }, }, }, - results: []gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + results: []policyTargetReferenceWithSectionName{ { - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: "gateway.networking.k8s.io", - Kind: "TLSRoute", - Name: "third", - }, + Group: "gateway.networking.k8s.io", + Kind: "TLSRoute", + Name: "third", + Namespace: "default", }, { - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: "gateway.networking.k8s.io", - Kind: "Gateway", - Name: "second", - }, + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "second", + Namespace: "default", }, }, }, @@ -409,13 +409,12 @@ func TestGetPolicyTargetRefs(t *testing.T) { }, }, }, - results: []gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + results: []policyTargetReferenceWithSectionName{ { - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: "gateway.networking.k8s.io", - Kind: "TLSRoute", - Name: "third", - }, + Group: "gateway.networking.k8s.io", + Kind: "TLSRoute", + Name: "third", + Namespace: "default", }, }, }, @@ -473,7 +472,7 @@ func TestGetPolicyTargetRefs(t *testing.T) { }, }, }, - results: []gwapiv1.LocalPolicyTargetReferenceWithSectionName{}, + results: []policyTargetReferenceWithSectionName{}, }, { name: "match expression", @@ -519,13 +518,12 @@ func TestGetPolicyTargetRefs(t *testing.T) { }, }, }, - results: []gwapiv1.LocalPolicyTargetReferenceWithSectionName{ + results: []policyTargetReferenceWithSectionName{ { - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: "gateway.networking.k8s.io", - Kind: "Gateway", - Name: "first", - }, + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "first", + Namespace: "default", }, }, }, @@ -573,13 +571,347 @@ func TestGetPolicyTargetRefs(t *testing.T) { }, }, }, - results: []gwapiv1.LocalPolicyTargetReferenceWithSectionName{}, + results: []policyTargetReferenceWithSectionName{}, + }, + { + name: "namespaces from same", + policy: egv1a1.PolicyTargetReferences{ + TargetSelectors: []egv1a1.TargetSelector{ + { + Kind: "Gateway", + Namespaces: &egv1a1.TargetSelectorNamespaces{ + From: egv1a1.TargetNamespaceFromSame, + }, + MatchLabels: map[string]string{ + "pick": "me", + }, + }, + }, + }, + targets: []*unstructured.Unstructured{ + { + Object: map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "Gateway", + "metadata": map[string]any{ + "name": "same-ns", + "namespace": "default", + "labels": map[string]any{ + "pick": "me", + }, + }, + }, + }, + { + Object: map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "Gateway", + "metadata": map[string]any{ + "name": "other-ns", + "namespace": "other", + "labels": map[string]any{ + "pick": "me", + }, + }, + }, + }, + }, + results: []policyTargetReferenceWithSectionName{ + { + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "same-ns", + Namespace: "default", + }, + }, + }, + { + name: "namespaces from all", + policy: egv1a1.PolicyTargetReferences{ + TargetSelectors: []egv1a1.TargetSelector{ + { + Kind: "Gateway", + Namespaces: &egv1a1.TargetSelectorNamespaces{ + From: egv1a1.TargetNamespaceFromAll, + }, + MatchLabels: map[string]string{ + "pick": "me", + }, + }, + }, + }, + targets: []*unstructured.Unstructured{ + { + Object: map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "Gateway", + "metadata": map[string]any{ + "name": "same-ns", + "namespace": "default", + "labels": map[string]any{ + "pick": "me", + }, + }, + }, + }, + { + Object: map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "Gateway", + "metadata": map[string]any{ + "name": "other-ns", + "namespace": "other", + "labels": map[string]any{ + "pick": "me", + }, + }, + }, + }, + }, + grants: []*gwapiv1b1.ReferenceGrant{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "allow-default-btp", + Namespace: "other", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Group: gwapiv1b1.Group(egv1a1.GroupVersion.Group), + Kind: gwapiv1b1.Kind("BackendTrafficPolicy"), + Namespace: gwapiv1b1.Namespace("default"), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Group: gwapiv1b1.Group(gwapiv1.GroupName), + Kind: gwapiv1b1.Kind("Gateway"), + }, + }, + }, + }, + }, + results: []policyTargetReferenceWithSectionName{ + { + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "same-ns", + Namespace: "default", + }, + { + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "other-ns", + Namespace: "other", + }, + }, + }, + { + name: "namespaces from selector", + policy: egv1a1.PolicyTargetReferences{ + TargetSelectors: []egv1a1.TargetSelector{ + { + Kind: "Gateway", + Namespaces: &egv1a1.TargetSelectorNamespaces{ + From: egv1a1.TargetNamespaceFromSelector, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "team": "blue", + }, + }, + }, + MatchLabels: map[string]string{ + "pick": "me", + }, + }, + }, + }, + targets: []*unstructured.Unstructured{ + { + Object: map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "Gateway", + "metadata": map[string]any{ + "name": "selected-ns", + "namespace": "selected", + "labels": map[string]any{ + "pick": "me", + }, + }, + }, + }, + { + Object: map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "Gateway", + "metadata": map[string]any{ + "name": "unselected-ns", + "namespace": "unselected", + "labels": map[string]any{ + "pick": "me", + }, + }, + }, + }, + }, + namespaces: []*corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "selected", + Labels: map[string]string{"team": "blue"}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "unselected", + Labels: map[string]string{"team": "green"}, + }, + }, + }, + grants: []*gwapiv1b1.ReferenceGrant{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "allow-default-btp", + Namespace: "selected", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Group: gwapiv1b1.Group(egv1a1.GroupVersion.Group), + Kind: gwapiv1b1.Kind("BackendTrafficPolicy"), + Namespace: gwapiv1b1.Namespace("default"), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Group: gwapiv1b1.Group(gwapiv1.GroupName), + Kind: gwapiv1b1.Kind("Gateway"), + }, + }, + }, + }, + }, + results: []policyTargetReferenceWithSectionName{ + { + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "selected-ns", + Namespace: "selected", + }, + }, + }, + { + name: "namespaces from selector requires known namespace labels", + policy: egv1a1.PolicyTargetReferences{ + TargetSelectors: []egv1a1.TargetSelector{ + { + Kind: "Gateway", + Namespaces: &egv1a1.TargetSelectorNamespaces{ + From: egv1a1.TargetNamespaceFromSelector, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "team": "blue", + }, + }, + }, + MatchLabels: map[string]string{ + "pick": "me", + }, + }, + }, + }, + targets: []*unstructured.Unstructured{ + { + Object: map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "Gateway", + "metadata": map[string]any{ + "name": "unknown-ns", + "namespace": "unknown", + "labels": map[string]any{ + "pick": "me", + }, + }, + }, + }, + }, + results: []policyTargetReferenceWithSectionName{}, + }, + { + name: "namespaces from all cross-namespace requires reference grant", + policy: egv1a1.PolicyTargetReferences{ + TargetSelectors: []egv1a1.TargetSelector{ + { + Kind: "Gateway", + Namespaces: &egv1a1.TargetSelectorNamespaces{ + From: egv1a1.TargetNamespaceFromAll, + }, + MatchLabels: map[string]string{ + "pick": "me", + }, + }, + }, + }, + targets: []*unstructured.Unstructured{ + { + Object: map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "Gateway", + "metadata": map[string]any{ + "name": "same-ns", + "namespace": "default", + "labels": map[string]any{ + "pick": "me", + }, + }, + }, + }, + { + Object: map[string]any{ + "apiVersion": "gateway.networking.k8s.io/v1", + "kind": "Gateway", + "metadata": map[string]any{ + "name": "other-ns", + "namespace": "other", + "labels": map[string]any{ + "pick": "me", + }, + }, + }, + }, + }, + results: []policyTargetReferenceWithSectionName{ + { + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "same-ns", + Namespace: "default", + }, + }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - results := getPolicyTargetRefs(tc.policy, tc.targets, "default") + namespaceMap := map[string]*corev1.Namespace{} + for _, ns := range tc.namespaces { + namespaceMap[ns.Name] = ns + } + + results := getPolicyTargetRefs( + tc.policy, + tc.targets, + crossNamespaceFrom{ + group: egv1a1.GroupVersion.Group, + kind: "BackendTrafficPolicy", + namespace: "default", + }, + tc.grants, + "default", + func(name string) *corev1.Namespace { + return namespaceMap[name] + }, + ) require.ElementsMatch(t, results, tc.results) }) } diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index 6e439ca02f..47a044f0c7 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -32,6 +32,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation" "k8s.io/utils/ptr" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/gatewayapi/resource" @@ -137,12 +138,22 @@ func (t *Translator) ProcessSecurityPolicies( // 4. Finally, the policies targeting Gateways // Build gateway policy maps, which are needed when processing the policies targeting xRoutes. - t.buildGatewayPolicyMapForSecurity(securityPolicies, gateways, gatewayMap, gatewayPolicyMap) + t.buildGatewayPolicyMapForSecurity(securityPolicies, gateways, gatewayMap, gatewayPolicyMap, resources.ReferenceGrants) // Process the policies targeting RouteRules (HTTP + TCP) for i, currPolicy := range securityPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) + routeMatches := getPolicySelectorTargetMatches(currPolicy.Spec.PolicyTargetReferences, routes, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "SecurityPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "SecurityPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + if len(routeMatches.Denied) > 0 { + policy, found := handledPolicies[policyName] + if !found { + policy = currPolicy + handledPolicies[policyName] = policy + res = append(res, policy) + } + setPolicyTargetRefNotPermittedStatus(&policy.Status, routeMatches.Denied, t.GatewayControllerName, policy.Generation) + } for _, currTarget := range targetRefs { // If the target is not a gateway, then it's an xRoute. If the section name is defined, then it's a route rule. if currTarget.Kind != resource.KindGateway && currTarget.SectionName != nil { @@ -160,7 +171,7 @@ func (t *Translator) ProcessSecurityPolicies( // Process the policies targeting xRoutes (HTTP + TCP) for i, currPolicy := range securityPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "SecurityPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) for _, currTarget := range targetRefs { // If the target is not a gateway, then it's an xRoute. If the section name is not defined, then it's a route. if currTarget.Kind != resource.KindGateway && currTarget.SectionName == nil { @@ -178,7 +189,17 @@ func (t *Translator) ProcessSecurityPolicies( // Process the policies targeting Listeners for i, currPolicy := range securityPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) + gatewayMatches := getPolicySelectorTargetMatches(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "SecurityPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "SecurityPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + if len(gatewayMatches.Denied) > 0 { + policy, found := handledPolicies[policyName] + if !found { + policy = currPolicy + handledPolicies[policyName] = policy + res = append(res, policy) + } + setPolicyTargetRefNotPermittedStatus(&policy.Status, gatewayMatches.Denied, t.GatewayControllerName, policy.Generation) + } for _, currTarget := range targetRefs { // If the target is a gateway and the section name is defined, then it's a listener. if currTarget.Kind == resource.KindGateway && currTarget.SectionName != nil { @@ -188,15 +209,17 @@ func (t *Translator) ProcessSecurityPolicies( handledPolicies[policyName] = policy res = append(res, policy) } + targetNamespace := namespaceForPolicyTargetRef(currTarget, currPolicy.Namespace, gatewayMatches.Allowed) - t.processSecurityPolicyForGateway(resources, xdsIR, gatewayMap, gatewayRouteMap, gatewayPolicyMerged, policy, currTarget) + t.processSecurityPolicyForGateway(resources, xdsIR, gatewayMap, gatewayRouteMap, gatewayPolicyMerged, policy, currTarget, targetNamespace) } } } // Process the policies targeting Gateways for i, currPolicy := range securityPolicies { policyName := utils.NamespacedName(currPolicy) - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) + gatewayMatches := getPolicySelectorTargetMatches(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "SecurityPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "SecurityPolicy", namespace: currPolicy.Namespace}, resources.ReferenceGrants, currPolicy.Namespace, t.GetNamespace) for _, currTarget := range targetRefs { // If the target is a gateway and the section name is not defined, then it's a gateway. if currTarget.Kind == resource.KindGateway && currTarget.SectionName == nil { @@ -206,8 +229,9 @@ func (t *Translator) ProcessSecurityPolicies( handledPolicies[policyName] = policy res = append(res, policy) } + targetNamespace := namespaceForPolicyTargetRef(currTarget, currPolicy.Namespace, gatewayMatches.Allowed) - t.processSecurityPolicyForGateway(resources, xdsIR, gatewayMap, gatewayRouteMap, gatewayPolicyMerged, policy, currTarget) + t.processSecurityPolicyForGateway(resources, xdsIR, gatewayMap, gatewayRouteMap, gatewayPolicyMerged, policy, currTarget, targetNamespace) } } } @@ -226,15 +250,17 @@ func (t *Translator) buildGatewayPolicyMapForSecurity( gateways []*GatewayContext, gatewayMap map[types.NamespacedName]*policyGatewayTargetContext, gatewayPolicyMap map[NamespacedNameWithSection]*egv1a1.SecurityPolicy, + referenceGrants []*gwapiv1b1.ReferenceGrant, ) { for _, currPolicy := range securityPolicies { - targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) + gatewayMatches := getPolicySelectorTargetMatches(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "SecurityPolicy", namespace: currPolicy.Namespace}, referenceGrants, currPolicy.Namespace, t.GetNamespace) + targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, crossNamespaceFrom{group: egv1a1.GroupVersion.Group, kind: "SecurityPolicy", namespace: currPolicy.Namespace}, referenceGrants, currPolicy.Namespace, t.GetNamespace) for _, currTarget := range targetRefs { if currTarget.Kind == resource.KindGateway { // Check if the gateway exists key := types.NamespacedName{ Name: string(currTarget.Name), - Namespace: currPolicy.Namespace, + Namespace: namespaceForPolicyTargetRef(currTarget, currPolicy.Namespace, gatewayMatches.Allowed), } gateway, ok := gatewayMap[key] if !ok { @@ -275,14 +301,14 @@ func (t *Translator) processSecurityPolicyForRoute( gatewayPolicyMerged *GatewayPolicyRouteMap, gatewayPolicyMap map[NamespacedNameWithSection]*egv1a1.SecurityPolicy, policy *egv1a1.SecurityPolicy, - currTarget gwapiv1.LocalPolicyTargetReferenceWithSectionName, + currTarget policyTargetReferenceWithSectionName, ) { var ( targetedRoute RouteContext resolveErr *status.PolicyResolveError ) - targetedRoute, resolveErr = resolveSecurityPolicyRouteTargetRef(policy, currTarget, routeMap) + targetedRoute, resolveErr = resolveSecurityPolicyRouteTargetRef(currTarget, routeMap) // Skip if the route is not found // It's not necessarily an error because the SecurityPolicy may be // reconciled by multiple controllers. And the other controller may @@ -498,7 +524,7 @@ func (t *Translator) processSecurityPolicyForRoute( key := policyTargetRouteKey{ Kind: string(currTarget.Kind), Name: string(currTarget.Name), - Namespace: policy.Namespace, + Namespace: string(currTarget.Namespace), } overriddenTargetsMessage := getOverriddenTargetsMessageForRoute(routeMap[key], currTarget.SectionName) if overriddenTargetsMessage != "" { @@ -521,14 +547,15 @@ func (t *Translator) processSecurityPolicyForGateway( gatewayRouteMap *GatewayPolicyRouteMap, gatewayPolicyMergedMap *GatewayPolicyRouteMap, policy *egv1a1.SecurityPolicy, - currTarget gwapiv1.LocalPolicyTargetReferenceWithSectionName, + currTarget policyTargetReferenceWithSectionName, + targetNamespace string, ) { var ( targetedGateway *GatewayContext resolveErr *status.PolicyResolveError ) - targetedGateway, resolveErr = resolveSecurityPolicyGatewayTargetRef(policy, currTarget, gatewayMap) + targetedGateway, resolveErr = resolveSecurityPolicyGatewayTargetRef(currTarget, targetNamespace, gatewayMap) // Skip if the gateway is not found // It's not necessarily an error because the SecurityPolicy may be // reconciled by multiple controllers. And the other controller may @@ -696,14 +723,14 @@ func validateBasicAuth(_ *egv1a1.BasicAuth) error { } func resolveSecurityPolicyGatewayTargetRef( - policy *egv1a1.SecurityPolicy, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, + targetNamespace string, gateways map[types.NamespacedName]*policyGatewayTargetContext, ) (*GatewayContext, *status.PolicyResolveError) { // Find the Gateway key := types.NamespacedName{ Name: string(target.Name), - Namespace: policy.Namespace, + Namespace: targetNamespace, } gateway, ok := gateways[key] @@ -761,15 +788,14 @@ func resolveSecurityPolicyGatewayTargetRef( } func resolveSecurityPolicyRouteTargetRef( - policy *egv1a1.SecurityPolicy, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, routes map[policyTargetRouteKey]*policyRouteTargetContext, ) (RouteContext, *status.PolicyResolveError) { // Check if the route exists key := policyTargetRouteKey{ Kind: string(target.Kind), Name: string(target.Name), - Namespace: policy.Namespace, + Namespace: string(target.Namespace), } route, ok := routes[key] @@ -824,7 +850,7 @@ func resolveSecurityPolicyRouteTargetRef( func (t *Translator) translateSecurityPolicyForRoute( policy *egv1a1.SecurityPolicy, route RouteContext, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, resources *resource.Resources, xdsIR resource.XdsIRMap, policyTargetGateway *types.NamespacedName, @@ -1050,7 +1076,7 @@ func (t *Translator) translateSecurityPolicyForRoute( func (t *Translator) translateSecurityPolicyForGateway( policy *egv1a1.SecurityPolicy, gtwCtx *GatewayContext, - target gwapiv1.LocalPolicyTargetReferenceWithSectionName, + target policyTargetReferenceWithSectionName, resources *resource.Resources, xdsIR resource.XdsIRMap, ) error { diff --git a/internal/gatewayapi/securitypolicy_test.go b/internal/gatewayapi/securitypolicy_test.go index 19ce7f08c5..5ef480a1cb 100644 --- a/internal/gatewayapi/securitypolicy_test.go +++ b/internal/gatewayapi/securitypolicy_test.go @@ -948,12 +948,11 @@ func Test_SecurityPolicy_TCP_Invalid_setsStatus_and_returns(t *testing.T) { SetRouteParentContext(tcpRoute, tcpRoute.Spec.ParentRefs[0]) // Create the target reference - target := gwapiv1.LocalPolicyTargetReferenceWithSectionName{ - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: gwapiv1.Group(gwapiv1.GroupVersion.Group), - Kind: resource.KindTCPRoute, - Name: "tcp-route", - }, + target := policyTargetReferenceWithSectionName{ + Group: gwapiv1.Group(gwapiv1.GroupVersion.Group), + Kind: resource.KindTCPRoute, + Name: "tcp-route", + Namespace: "default", } // Create route map @@ -1034,12 +1033,11 @@ func Test_SecurityPolicy_HTTP_Invalid_setsStatus_and_returns(t *testing.T) { SetRouteParentContext(httpRoute, httpRoute.Spec.ParentRefs[0]) // Create the target reference - target := gwapiv1.LocalPolicyTargetReferenceWithSectionName{ - LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{ - Group: gwapiv1.Group(gwapiv1.GroupVersion.Group), - Kind: resource.KindHTTPRoute, - Name: "http-route", - }, + target := policyTargetReferenceWithSectionName{ + Group: gwapiv1.Group(gwapiv1.GroupVersion.Group), + Kind: resource.KindHTTPRoute, + Name: "http-route", + Namespace: "default", } // Create route map diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-invalid-referencegrant.in.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-invalid-referencegrant.in.yaml new file mode 100644 index 0000000000..ff05db314b --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-invalid-referencegrant.in.yaml @@ -0,0 +1,80 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: policy-target-ns + name: target-gateway + labels: + policy: selected + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: policy-target-ns + name: target-route + spec: + parentRefs: + - name: target-gateway + sectionName: http + rules: + - matches: + - path: + value: / + backendRefs: + - name: target-service + port: 8080 +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: policy-ns + name: cross-ns-policy + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: Gateway + namespaces: + from: All + matchLabels: + policy: selected + useClientProtocol: true +namespaces: +- apiVersion: v1 + kind: Namespace + metadata: + name: policy-target-ns +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: policy-target-ns + name: target-service + spec: + ports: + - port: 8080 + name: http + protocol: TCP +endpointSlices: +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-target-service + namespace: policy-target-ns + labels: + kubernetes.io/service-name: target-service + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - 8.8.8.8 + conditions: + ready: true diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-invalid-referencegrant.out.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-invalid-referencegrant.out.yaml new file mode 100644 index 0000000000..4a883cbcf0 --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-invalid-referencegrant.out.yaml @@ -0,0 +1,199 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: cross-ns-policy + namespace: policy-ns + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: Gateway + matchLabels: + policy: selected + namespaces: + from: All + useClientProtocol: true + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: target-gateway + namespace: policy-target-ns + conditions: + - lastTransitionTime: null + message: Target Gateway policy-target-ns/target-gateway is not permitted by + any ReferenceGrant. + reason: RefNotPermitted + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + labels: + policy: selected + name: target-gateway + namespace: policy-target-ns + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: target-route + namespace: policy-target-ns + spec: + parentRefs: + - name: target-gateway + sectionName: http + rules: + - backendRefs: + - name: target-service + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: target-gateway + sectionName: http +infraIR: + policy-target-ns/target-gateway: + proxy: + listeners: + - name: policy-target-ns/target-gateway/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: target-gateway + gateway.envoyproxy.io/owning-gateway-namespace: policy-target-ns + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: policy-target-ns/target-gateway + namespace: envoy-gateway-system +xdsIR: + policy-target-ns/target-gateway: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-policy-target-ns-target-gateway-9aed6540 + namespace: envoy-gateway-system + sectionName: "8080" + name: policy-target-ns/target-gateway + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-policy-target-ns-target-gateway-9aed6540 + namespace: envoy-gateway-system + sectionName: "8080" + name: policy-target-ns/target-gateway + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: target-gateway + namespace: policy-target-ns + sectionName: http + name: policy-target-ns/target-gateway/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: target-route + namespace: policy-target-ns + name: httproute/policy-target-ns/target-route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.8.8 + port: 8080 + metadata: + kind: Service + name: target-service + namespace: policy-target-ns + sectionName: "8080" + name: httproute/policy-target-ns/target-route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: target-route + namespace: policy-target-ns + name: httproute/policy-target-ns/target-route/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-gateway-target.in.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-gateway-target.in.yaml new file mode 100644 index 0000000000..b86e657918 --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-gateway-target.in.yaml @@ -0,0 +1,155 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: policy-target-a + name: allowed-gateway + labels: + policy: selected + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: policy-target-b + name: denied-gateway + labels: + policy: selected + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: policy-target-a + name: allowed-route + spec: + parentRefs: + - name: allowed-gateway + sectionName: http + rules: + - matches: + - path: + value: / + backendRefs: + - name: allowed-service + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: policy-target-b + name: denied-route + spec: + parentRefs: + - name: denied-gateway + sectionName: http + rules: + - matches: + - path: + value: / + backendRefs: + - name: denied-service + port: 8080 +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: policy-ns + name: cross-ns-policy + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: Gateway + namespaces: + from: All + matchLabels: + policy: selected + useClientProtocol: true +namespaces: +- apiVersion: v1 + kind: Namespace + metadata: + name: policy-target-a +- apiVersion: v1 + kind: Namespace + metadata: + name: policy-target-b +referenceGrants: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: ReferenceGrant + metadata: + namespace: policy-target-a + name: allow-btp + spec: + from: + - group: gateway.envoyproxy.io + kind: BackendTrafficPolicy + namespace: policy-ns + to: + - group: gateway.networking.k8s.io + kind: Gateway + name: allowed-gateway +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: policy-target-a + name: allowed-service + spec: + ports: + - port: 8080 + name: http + protocol: TCP +- apiVersion: v1 + kind: Service + metadata: + namespace: policy-target-b + name: denied-service + spec: + ports: + - port: 8080 + name: http + protocol: TCP +endpointSlices: +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-allowed-service + namespace: policy-target-a + labels: + kubernetes.io/service-name: allowed-service + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - 8.8.8.8 + conditions: + ready: true +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-denied-service + namespace: policy-target-b + labels: + kubernetes.io/service-name: denied-service + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - 8.8.4.4 + conditions: + ready: true diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-gateway-target.out.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-gateway-target.out.yaml new file mode 100644 index 0000000000..3350d1a7d1 --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-gateway-target.out.yaml @@ -0,0 +1,382 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: cross-ns-policy + namespace: policy-ns + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: Gateway + matchLabels: + policy: selected + namespaces: + from: All + useClientProtocol: true + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: denied-gateway + namespace: policy-target-b + conditions: + - lastTransitionTime: null + message: Target Gateway policy-target-b/denied-gateway is not permitted by + any ReferenceGrant. + reason: RefNotPermitted + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: allowed-gateway + namespace: policy-target-a + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + labels: + policy: selected + name: allowed-gateway + namespace: policy-target-a + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + labels: + policy: selected + name: denied-gateway + namespace: policy-target-b + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: allowed-route + namespace: policy-target-a + spec: + parentRefs: + - name: allowed-gateway + sectionName: http + rules: + - backendRefs: + - name: allowed-service + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: allowed-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: denied-route + namespace: policy-target-b + spec: + parentRefs: + - name: denied-gateway + sectionName: http + rules: + - backendRefs: + - name: denied-service + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: denied-gateway + sectionName: http +infraIR: + policy-target-a/allowed-gateway: + proxy: + listeners: + - name: policy-target-a/allowed-gateway/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: allowed-gateway + gateway.envoyproxy.io/owning-gateway-namespace: policy-target-a + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: policy-target-a/allowed-gateway + namespace: envoy-gateway-system + policy-target-b/denied-gateway: + proxy: + listeners: + - name: policy-target-b/denied-gateway/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: denied-gateway + gateway.envoyproxy.io/owning-gateway-namespace: policy-target-b + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: policy-target-b/denied-gateway + namespace: envoy-gateway-system +xdsIR: + policy-target-a/allowed-gateway: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-policy-target-a-allowed-gateway-720a9890 + namespace: envoy-gateway-system + sectionName: "8080" + name: policy-target-a/allowed-gateway + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-policy-target-a-allowed-gateway-720a9890 + namespace: envoy-gateway-system + sectionName: "8080" + name: policy-target-a/allowed-gateway + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: allowed-gateway + namespace: policy-target-a + sectionName: http + name: policy-target-a/allowed-gateway/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: allowed-route + namespace: policy-target-a + name: httproute/policy-target-a/allowed-route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.8.8 + port: 8080 + metadata: + kind: Service + name: allowed-service + namespace: policy-target-a + sectionName: "8080" + name: httproute/policy-target-a/allowed-route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: allowed-route + namespace: policy-target-a + policies: + - kind: BackendTrafficPolicy + name: cross-ns-policy + namespace: policy-ns + name: httproute/policy-target-a/allowed-route/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + traffic: {} + useClientProtocol: true + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + policy-target-b/denied-gateway: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-policy-target-b-denied-gateway-56a9c11b + namespace: envoy-gateway-system + sectionName: "8080" + name: policy-target-b/denied-gateway + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-policy-target-b-denied-gateway-56a9c11b + namespace: envoy-gateway-system + sectionName: "8080" + name: policy-target-b/denied-gateway + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: denied-gateway + namespace: policy-target-b + sectionName: http + name: policy-target-b/denied-gateway/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: denied-route + namespace: policy-target-b + name: httproute/policy-target-b/denied-route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.4.4 + port: 8080 + metadata: + kind: Service + name: denied-service + namespace: policy-target-b + sectionName: "8080" + name: httproute/policy-target-b/denied-route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: denied-route + namespace: policy-target-b + name: httproute/policy-target-b/denied-route/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-route-target.in.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-route-target.in.yaml new file mode 100644 index 0000000000..dbc3564596 --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-route-target.in.yaml @@ -0,0 +1,149 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: gateway-ns + name: mixed-gateway # this gateway has two attached routes, one with ReferenceGrant and the other without + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: policy-target-a + name: allowed-route + labels: + policy: selected + spec: + parentRefs: + - name: mixed-gateway + namespace: gateway-ns + sectionName: http + rules: + - matches: + - path: + value: /allowed + backendRefs: + - name: allowed-service + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: policy-target-b + name: denied-route + labels: + policy: selected + spec: + parentRefs: + - name: mixed-gateway + namespace: gateway-ns + sectionName: http + rules: + - matches: + - path: + value: /denied + backendRefs: + - name: denied-service + port: 8080 +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: policy-ns + name: cross-ns-policy + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespaces: + from: All + matchLabels: + policy: selected + useClientProtocol: true +namespaces: +- apiVersion: v1 + kind: Namespace + metadata: + name: policy-target-a +- apiVersion: v1 + kind: Namespace + metadata: + name: policy-target-b +referenceGrants: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: ReferenceGrant + metadata: + namespace: policy-target-a + name: allow-btp + spec: + from: + - group: gateway.envoyproxy.io + kind: BackendTrafficPolicy + namespace: policy-ns + to: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: allowed-route +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: policy-target-a + name: allowed-service + spec: + ports: + - port: 8080 + name: http + protocol: TCP +- apiVersion: v1 + kind: Service + metadata: + namespace: policy-target-b + name: denied-service + spec: + ports: + - port: 8080 + name: http + protocol: TCP +endpointSlices: +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-allowed-service + namespace: policy-target-a + labels: + kubernetes.io/service-name: allowed-service + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - 8.8.8.8 + conditions: + ready: true +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-denied-service + namespace: policy-target-b + labels: + kubernetes.io/service-name: denied-service + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - 8.8.4.4 + conditions: + ready: true diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-route-target.out.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-route-target.out.yaml new file mode 100644 index 0000000000..100f261165 --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-partial-referencegrant-route-target.out.yaml @@ -0,0 +1,278 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: cross-ns-policy + namespace: policy-ns + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: HTTPRoute + matchLabels: + policy: selected + namespaces: + from: All + useClientProtocol: true + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: mixed-gateway + namespace: gateway-ns + sectionName: http + conditions: + - lastTransitionTime: null + message: Target HTTPRoute policy-target-b/denied-route is not permitted by + any ReferenceGrant. + reason: RefNotPermitted + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: mixed-gateway + namespace: gateway-ns + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + labels: + policy: selected + name: allowed-route + namespace: policy-target-a + spec: + parentRefs: + - name: mixed-gateway + namespace: gateway-ns + sectionName: http + rules: + - backendRefs: + - name: allowed-service + port: 8080 + matches: + - path: + value: /allowed + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: mixed-gateway + namespace: gateway-ns + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + labels: + policy: selected + name: denied-route + namespace: policy-target-b + spec: + parentRefs: + - name: mixed-gateway + namespace: gateway-ns + sectionName: http + rules: + - backendRefs: + - name: denied-service + port: 8080 + matches: + - path: + value: /denied + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: mixed-gateway + namespace: gateway-ns + sectionName: http +infraIR: + gateway-ns/mixed-gateway: + proxy: + listeners: + - name: gateway-ns/mixed-gateway/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: mixed-gateway + gateway.envoyproxy.io/owning-gateway-namespace: gateway-ns + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: gateway-ns/mixed-gateway + namespace: envoy-gateway-system +xdsIR: + gateway-ns/mixed-gateway: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-gateway-ns-mixed-gateway-b951d52e + namespace: envoy-gateway-system + sectionName: "8080" + name: gateway-ns/mixed-gateway + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-gateway-ns-mixed-gateway-b951d52e + namespace: envoy-gateway-system + sectionName: "8080" + name: gateway-ns/mixed-gateway + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: mixed-gateway + namespace: gateway-ns + sectionName: http + name: gateway-ns/mixed-gateway/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: allowed-route + namespace: policy-target-a + name: httproute/policy-target-a/allowed-route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.8.8 + port: 8080 + metadata: + kind: Service + name: allowed-service + namespace: policy-target-a + sectionName: "8080" + name: httproute/policy-target-a/allowed-route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: allowed-route + namespace: policy-target-a + policies: + - kind: BackendTrafficPolicy + name: cross-ns-policy + namespace: policy-ns + name: httproute/policy-target-a/allowed-route/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: /allowed + traffic: {} + useClientProtocol: true + - destination: + metadata: + kind: HTTPRoute + name: denied-route + namespace: policy-target-b + name: httproute/policy-target-b/denied-route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.4.4 + port: 8080 + metadata: + kind: Service + name: denied-service + namespace: policy-target-b + sectionName: "8080" + name: httproute/policy-target-b/denied-route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: denied-route + namespace: policy-target-b + name: httproute/policy-target-b/denied-route/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: /denied + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-gateway-target.in.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-gateway-target.in.yaml new file mode 100644 index 0000000000..9926b3880e --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-gateway-target.in.yaml @@ -0,0 +1,122 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: gateway-ns + name: test-gateway + labels: + team: blue + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Selector + selector: + matchLabels: + team: blue +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: route-ns + name: test-route + spec: + parentRefs: + - name: test-gateway + namespace: gateway-ns + sectionName: http + rules: + - matches: + - path: + value: / + backendRefs: + - name: backend + port: 8080 +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: policy-ns + name: cross-ns-selector-policy + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: Gateway + namespaces: + from: Selector + selector: + matchLabels: + team: blue + matchLabels: + team: blue + cors: + allowOrigins: + - https://example.com + allowMethods: + - GET + allowHeaders: + - x-example + exposeHeaders: + - x-exposed + maxAge: 1000s +namespaces: +- apiVersion: v1 + kind: Namespace + metadata: + name: gateway-ns + labels: + team: blue +- apiVersion: v1 + kind: Namespace + metadata: + name: route-ns + labels: + team: blue +referenceGrants: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: ReferenceGrant + metadata: + namespace: gateway-ns + name: allow-selector-sp + spec: + from: + - group: gateway.envoyproxy.io + kind: SecurityPolicy + namespace: policy-ns + to: + - group: gateway.networking.k8s.io + kind: Gateway + name: test-gateway +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: route-ns + name: backend + spec: + ports: + - port: 8080 + name: http + protocol: TCP +endpointSlices: +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-backend + namespace: route-ns + labels: + kubernetes.io/service-name: backend + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - 8.8.4.4 + conditions: + ready: true diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-gateway-target.out.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-gateway-target.out.yaml new file mode 100644 index 0000000000..21c07ceb69 --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-gateway-target.out.yaml @@ -0,0 +1,231 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + labels: + team: blue + name: test-gateway + namespace: gateway-ns + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: Selector + selector: + matchLabels: + team: blue + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: test-route + namespace: route-ns + spec: + parentRefs: + - name: test-gateway + namespace: gateway-ns + sectionName: http + rules: + - backendRefs: + - name: backend + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: test-gateway + namespace: gateway-ns + sectionName: http +infraIR: + gateway-ns/test-gateway: + proxy: + listeners: + - name: gateway-ns/test-gateway/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: test-gateway + gateway.envoyproxy.io/owning-gateway-namespace: gateway-ns + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: gateway-ns/test-gateway + namespace: envoy-gateway-system +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: cross-ns-selector-policy + namespace: policy-ns + spec: + cors: + allowHeaders: + - x-example + allowMethods: + - GET + allowOrigins: + - https://example.com + exposeHeaders: + - x-exposed + maxAge: 1000s + targetSelectors: + - group: gateway.networking.k8s.io + kind: Gateway + matchLabels: + team: blue + namespaces: + from: Selector + selector: + matchLabels: + team: blue + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: test-gateway + namespace: gateway-ns + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +xdsIR: + gateway-ns/test-gateway: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-gateway-ns-test-gateway-85a70075 + namespace: envoy-gateway-system + sectionName: "8080" + name: gateway-ns/test-gateway + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-gateway-ns-test-gateway-85a70075 + namespace: envoy-gateway-system + sectionName: "8080" + name: gateway-ns/test-gateway + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: test-gateway + namespace: gateway-ns + sectionName: http + name: gateway-ns/test-gateway/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: test-route + namespace: route-ns + name: httproute/route-ns/test-route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.4.4 + port: 8080 + metadata: + kind: Service + name: backend + namespace: route-ns + sectionName: "8080" + name: httproute/route-ns/test-route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: test-route + namespace: route-ns + name: httproute/route-ns/test-route/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + security: + cors: + allowHeaders: + - x-example + allowMethods: + - GET + allowOrigins: + - distinct: false + exact: https://example.com + name: "" + exposeHeaders: + - x-exposed + maxAge: 16m40s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-route-target.in.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-route-target.in.yaml new file mode 100644 index 0000000000..52e973dac5 --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-route-target.in.yaml @@ -0,0 +1,120 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: gateway-ns + name: test-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Selector + selector: + matchLabels: + team: blue +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: route-ns + name: test-route + labels: + team: blue + spec: + parentRefs: + - name: test-gateway + namespace: gateway-ns + sectionName: http + rules: + - matches: + - path: + value: / + backendRefs: + - name: backend + port: 8080 +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: policy-ns + name: cross-ns-selector-policy + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespaces: + from: Selector + selector: + matchLabels: + team: blue + matchLabels: + team: blue + cors: + allowOrigins: + - https://example.com + allowMethods: + - GET + allowHeaders: + - x-example + exposeHeaders: + - x-exposed + maxAge: 1000s +namespaces: +- apiVersion: v1 + kind: Namespace + metadata: + name: route-ns + labels: + team: blue +- apiVersion: v1 + kind: Namespace + metadata: + name: gateway-ns +referenceGrants: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: ReferenceGrant + metadata: + namespace: route-ns + name: allow-selector-sp + spec: + from: + - group: gateway.envoyproxy.io + kind: SecurityPolicy + namespace: policy-ns + to: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: test-route +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: route-ns + name: backend + spec: + ports: + - port: 8080 + name: http + protocol: TCP +endpointSlices: +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-backend + namespace: route-ns + labels: + kubernetes.io/service-name: backend + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - 8.8.4.4 + conditions: + ready: true diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-route-target.out.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-route-target.out.yaml new file mode 100644 index 0000000000..5c1240234d --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant-route-target.out.yaml @@ -0,0 +1,232 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: test-gateway + namespace: gateway-ns + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: Selector + selector: + matchLabels: + team: blue + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + labels: + team: blue + name: test-route + namespace: route-ns + spec: + parentRefs: + - name: test-gateway + namespace: gateway-ns + sectionName: http + rules: + - backendRefs: + - name: backend + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: test-gateway + namespace: gateway-ns + sectionName: http +infraIR: + gateway-ns/test-gateway: + proxy: + listeners: + - name: gateway-ns/test-gateway/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: test-gateway + gateway.envoyproxy.io/owning-gateway-namespace: gateway-ns + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: gateway-ns/test-gateway + namespace: envoy-gateway-system +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + name: cross-ns-selector-policy + namespace: policy-ns + spec: + cors: + allowHeaders: + - x-example + allowMethods: + - GET + allowOrigins: + - https://example.com + exposeHeaders: + - x-exposed + maxAge: 1000s + targetSelectors: + - group: gateway.networking.k8s.io + kind: HTTPRoute + matchLabels: + team: blue + namespaces: + from: Selector + selector: + matchLabels: + team: blue + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: test-gateway + namespace: gateway-ns + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +xdsIR: + gateway-ns/test-gateway: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-gateway-ns-test-gateway-85a70075 + namespace: envoy-gateway-system + sectionName: "8080" + name: gateway-ns/test-gateway + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-gateway-ns-test-gateway-85a70075 + namespace: envoy-gateway-system + sectionName: "8080" + name: gateway-ns/test-gateway + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: test-gateway + namespace: gateway-ns + sectionName: http + name: gateway-ns/test-gateway/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: test-route + namespace: route-ns + name: httproute/route-ns/test-route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.4.4 + port: 8080 + metadata: + kind: Service + name: backend + namespace: route-ns + sectionName: "8080" + name: httproute/route-ns/test-route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: test-route + namespace: route-ns + name: httproute/route-ns/test-route/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + security: + cors: + allowHeaders: + - x-example + allowMethods: + - GET + allowOrigins: + - distinct: false + exact: https://example.com + name: "" + exposeHeaders: + - x-exposed + maxAge: 16m40s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant.in.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant.in.yaml new file mode 100644 index 0000000000..c8d51a76a8 --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant.in.yaml @@ -0,0 +1,198 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: policy-target-a + name: target-gateway + labels: + policy: selected + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: policy-target-selector + name: selector-gateway + labels: + policy: namespace-selector + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: policy-target-a + name: target-route + spec: + parentRefs: + - name: target-gateway + sectionName: http + rules: + - matches: + - path: + value: / + backendRefs: + - name: target-service + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: policy-target-selector + name: selector-route + spec: + parentRefs: + - name: selector-gateway + sectionName: http + rules: + - matches: + - path: + value: / + backendRefs: + - name: selector-service + port: 8080 +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: policy-ns + name: cross-ns-policy + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: Gateway + namespaces: + from: All + matchLabels: + policy: selected + useClientProtocol: true +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: policy-ns + name: cross-ns-selector-policy + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespaces: + from: Selector + selector: + matchLabels: + team: blue + matchLabels: + policy: namespace-selector + cors: + allowOrigins: + - https://example.com + allowMethods: + - GET + allowHeaders: + - x-example + exposeHeaders: + - x-exposed + maxAge: 1000s +namespaces: +- apiVersion: v1 + kind: Namespace + metadata: + name: policy-target-a +- apiVersion: v1 + kind: Namespace + metadata: + name: policy-target-selector + labels: + team: blue +referenceGrants: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: ReferenceGrant + metadata: + namespace: policy-target-a + name: allow-btp + spec: + from: + - group: gateway.envoyproxy.io + kind: BackendTrafficPolicy + namespace: policy-ns + to: + - group: gateway.networking.k8s.io + kind: Gateway + name: target-gateway +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: ReferenceGrant + metadata: + namespace: policy-target-selector + name: allow-selector-btp + spec: + from: + - group: gateway.envoyproxy.io + kind: SecurityPolicy + namespace: policy-ns + to: + - group: gateway.networking.k8s.io + kind: Gateway + name: selector-gateway +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: policy-target-a + name: target-service + spec: + ports: + - port: 8080 + name: http + protocol: TCP +- apiVersion: v1 + kind: Service + metadata: + namespace: policy-target-selector + name: selector-service + spec: + ports: + - port: 8080 + name: http + protocol: TCP +endpointSlices: +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-target-service + namespace: policy-target-a + labels: + kubernetes.io/service-name: target-service + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - 8.8.8.8 + conditions: + ready: true +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-selector-service + namespace: policy-target-selector + labels: + kubernetes.io/service-name: selector-service + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8080 + endpoints: + - addresses: + - 8.8.4.4 + conditions: + ready: true diff --git a/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant.out.yaml b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant.out.yaml new file mode 100644 index 0000000000..7e4a8cbe6f --- /dev/null +++ b/internal/gatewayapi/testdata/policy-cross-namespace-targetselector-valid-referencegrant.out.yaml @@ -0,0 +1,369 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: cross-ns-policy + namespace: policy-ns + spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: Gateway + matchLabels: + policy: selected + namespaces: + from: All + useClientProtocol: true + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: target-gateway + namespace: policy-target-a + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + labels: + policy: selected + name: target-gateway + namespace: policy-target-a + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + labels: + policy: namespace-selector + name: selector-gateway + namespace: policy-target-selector + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: target-route + namespace: policy-target-a + spec: + parentRefs: + - name: target-gateway + sectionName: http + rules: + - backendRefs: + - name: target-service + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: target-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: selector-route + namespace: policy-target-selector + spec: + parentRefs: + - name: selector-gateway + sectionName: http + rules: + - backendRefs: + - name: selector-service + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: selector-gateway + sectionName: http +infraIR: + policy-target-a/target-gateway: + proxy: + listeners: + - name: policy-target-a/target-gateway/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: target-gateway + gateway.envoyproxy.io/owning-gateway-namespace: policy-target-a + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: policy-target-a/target-gateway + namespace: envoy-gateway-system + policy-target-selector/selector-gateway: + proxy: + listeners: + - name: policy-target-selector/selector-gateway/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: selector-gateway + gateway.envoyproxy.io/owning-gateway-namespace: policy-target-selector + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: policy-target-selector/selector-gateway + namespace: envoy-gateway-system +xdsIR: + policy-target-a/target-gateway: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-policy-target-a-target-gateway-e4ecf2e1 + namespace: envoy-gateway-system + sectionName: "8080" + name: policy-target-a/target-gateway + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-policy-target-a-target-gateway-e4ecf2e1 + namespace: envoy-gateway-system + sectionName: "8080" + name: policy-target-a/target-gateway + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: target-gateway + namespace: policy-target-a + sectionName: http + name: policy-target-a/target-gateway/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: target-route + namespace: policy-target-a + name: httproute/policy-target-a/target-route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.8.8 + port: 8080 + metadata: + kind: Service + name: target-service + namespace: policy-target-a + sectionName: "8080" + name: httproute/policy-target-a/target-route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: target-route + namespace: policy-target-a + policies: + - kind: BackendTrafficPolicy + name: cross-ns-policy + namespace: policy-ns + name: httproute/policy-target-a/target-route/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + traffic: {} + useClientProtocol: true + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + policy-target-selector/selector-gateway: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-policy-target-selector-selector-gateway-45fe5771 + namespace: envoy-gateway-system + sectionName: "8080" + name: policy-target-selector/selector-gateway + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-policy-target-selector-selector-gateway-45fe5771 + namespace: envoy-gateway-system + sectionName: "8080" + name: policy-target-selector/selector-gateway + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: selector-gateway + namespace: policy-target-selector + sectionName: http + name: policy-target-selector/selector-gateway/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: selector-route + namespace: policy-target-selector + name: httproute/policy-target-selector/selector-route/rule/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.4.4 + port: 8080 + metadata: + kind: Service + name: selector-service + namespace: policy-target-selector + sectionName: "8080" + name: httproute/policy-target-selector/selector-route/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: selector-route + namespace: policy-target-selector + name: httproute/policy-target-selector/selector-route/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 005eb6a426..63eca0bda4 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -263,6 +263,8 @@ func (t *Translator) Translate(resources *resource.Resources) (*TranslateResult, resources.BackendTrafficPolicies, routesToObjects(resources), acceptedGateways, + resources.ReferenceGrants, + t.GetNamespace, ) } @@ -339,7 +341,7 @@ func (t *Translator) Translate(resources *resource.Resources) (*TranslateResult, resources.EnvoyExtensionPolicies, acceptedGateways, routes, resources, xdsIR) extServerPolicies, err := t.ProcessExtensionServerPolicies( - resources.ExtensionServerPolicies, acceptedGateways, xdsIR) + resources.ExtensionServerPolicies, acceptedGateways, resources.ReferenceGrants, xdsIR) if err != nil { errs = errors.Join(errs, err) } diff --git a/internal/gatewayapi/validate.go b/internal/gatewayapi/validate.go index 82fa2061d2..ae8315b0a9 100644 --- a/internal/gatewayapi/validate.go +++ b/internal/gatewayapi/validate.go @@ -834,44 +834,7 @@ func (t *Translator) validateConflictedLayer4Listeners(gateways []*GatewayContex } func (t *Translator) validateCrossNamespaceRef(from crossNamespaceFrom, to crossNamespaceTo, referenceGrants []*gwapiv1b1.ReferenceGrant) bool { - for _, referenceGrant := range referenceGrants { - // The ReferenceGrant must be defined in the namespace of - // the "to" (the referent). - if referenceGrant.Namespace != to.namespace { - continue - } - - // Check if the ReferenceGrant has a matching "from". - var fromAllowed bool - for _, refGrantFrom := range referenceGrant.Spec.From { - if string(refGrantFrom.Namespace) == from.namespace && string(refGrantFrom.Group) == from.group && string(refGrantFrom.Kind) == from.kind { - fromAllowed = true - break - } - } - if !fromAllowed { - continue - } - - // Check if the ReferenceGrant has a matching "to". - var toAllowed bool - for _, refGrantTo := range referenceGrant.Spec.To { - if string(refGrantTo.Group) == to.group && string(refGrantTo.Kind) == to.kind && (refGrantTo.Name == nil || *refGrantTo.Name == "" || string(*refGrantTo.Name) == to.name) { - toAllowed = true - break - } - } - if !toAllowed { - continue - } - - // If we got here, both the "from" and the "to" were allowed by this - // reference grant. - return true - } - - // If we got here, no reference policy or reference grant allowed both the "from" and "to". - return false + return isCrossNamespacePolicyTargetRefAllowed(from, to, referenceGrants) } // Checks if a hostname is valid according to RFC 1123 and gateway API's requirement that it not be an IP address diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index 582bf000e9..9dc145d8a8 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -511,6 +511,14 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques } } + if err = r.processPolicyTargetReferenceGrants(ctx, gwcResource, gwcResourceMapping); err != nil { + if isTransientError(err) { + gcLogger.Error(err, "transient error processing policy target ReferenceGrants") + return reconcile.Result{}, err + } + gcLogger.Error(err, "failed to process policy target ReferenceGrants for GatewayClass") + } + if err = r.processExtensionServerPolicies(ctx, gwcResource); err != nil { if isTransientError(err) { gcLogger.Error(err, "transient error processing ExtensionServerPolicies") @@ -1601,6 +1609,126 @@ func (r *gatewayAPIReconciler) findReferenceGrant(ctx context.Context, from, to return nil, nil } +// processPolicyTargetReferenceGrants finds the ReferenceGrants that allow policies in this GatewayClass to reference resources in other namespaces, and adds them to the resourceTree. +func (r *gatewayAPIReconciler) processPolicyTargetReferenceGrants( + ctx context.Context, + resourceTree *resource.Resources, + resourceMap *resourceMappings, +) error { + targetNamespaces := sets.New[string]() + for _, gateway := range resourceTree.Gateways { + targetNamespaces.Insert(gateway.Namespace) + } + for _, route := range resourceTree.HTTPRoutes { + targetNamespaces.Insert(route.Namespace) + } + for _, route := range resourceTree.GRPCRoutes { + targetNamespaces.Insert(route.Namespace) + } + for _, route := range resourceTree.TLSRoutes { + targetNamespaces.Insert(route.Namespace) + } + for _, route := range resourceTree.TCPRoutes { + targetNamespaces.Insert(route.Namespace) + } + for _, route := range resourceTree.UDPRoutes { + targetNamespaces.Insert(route.Namespace) + } + if targetNamespaces.Len() == 0 { + return nil + } + + policyFromNamespaces := map[string]sets.Set[string]{ + resource.KindClientTrafficPolicy: sets.New[string](), + resource.KindBackendTrafficPolicy: sets.New[string](), + resource.KindEnvoyExtensionPolicy: sets.New[string](), + } + for _, policy := range resourceTree.ClientTrafficPolicies { + policyFromNamespaces[resource.KindClientTrafficPolicy].Insert(policy.Namespace) + } + for _, policy := range resourceTree.BackendTrafficPolicies { + policyFromNamespaces[resource.KindBackendTrafficPolicy].Insert(policy.Namespace) + } + for _, policy := range resourceTree.EnvoyExtensionPolicies { + policyFromNamespaces[resource.KindEnvoyExtensionPolicy].Insert(policy.Namespace) + } + + allowedTargetKinds := map[string]sets.Set[string]{ + resource.KindClientTrafficPolicy: sets.New[string](resource.KindGateway), + resource.KindBackendTrafficPolicy: sets.New[string]( + resource.KindGateway, + resource.KindHTTPRoute, + resource.KindGRPCRoute, + resource.KindTLSRoute, + resource.KindTCPRoute, + resource.KindUDPRoute, + ), + resource.KindEnvoyExtensionPolicy: sets.New[string]( + resource.KindGateway, + resource.KindHTTPRoute, + resource.KindGRPCRoute, + resource.KindTLSRoute, + resource.KindTCPRoute, + resource.KindUDPRoute, + ), + } + + refGrantList := new(gwapiv1b1.ReferenceGrantList) + if err := r.client.List(ctx, refGrantList); err != nil { + return fmt.Errorf("failed to list ReferenceGrants: %w", err) + } + + for i := range refGrantList.Items { + refGrant := &refGrantList.Items[i] + if !targetNamespaces.Has(refGrant.Namespace) { + continue + } + if !policyTargetReferenceGrantAllowed(refGrant, policyFromNamespaces, allowedTargetKinds) { + continue + } + + key := utils.NamespacedName(refGrant).String() + if resourceMap.allAssociatedReferenceGrants.Has(key) { + continue + } + resourceMap.allAssociatedReferenceGrants.Insert(key) + resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) + r.log.Info("added policy target ReferenceGrant to resource map", "namespace", refGrant.Namespace, "name", refGrant.Name) + } + + return nil +} + +// policyTargetReferenceGrantAllowed checks if the ReferenceGrant allows any of the policies in this GatewayClass to reference resources in other namespaces. +// This is a coarse check that looks at the "from" and "to" of the ReferenceGrant to see if it potentially allows any valid reference from policies to gateways/routes. +// The detailed check is done in the Gateway API IR translation, where we check if the ReferenceGrant allows the specific reference from the specific policy to the specific gateway/route. +func policyTargetReferenceGrantAllowed( + refGrant *gwapiv1b1.ReferenceGrant, + policyFromNamespaces map[string]sets.Set[string], + allowedTargetKinds map[string]sets.Set[string], +) bool { + for _, refGrantFrom := range refGrant.Spec.From { + if string(refGrantFrom.Group) != egv1a1.GroupVersion.Group { + continue + } + fromNamespaces, ok := policyFromNamespaces[string(refGrantFrom.Kind)] + if !ok || !fromNamespaces.Has(string(refGrantFrom.Namespace)) { + continue + } + + for _, refGrantTo := range refGrant.Spec.To { + if string(refGrantTo.Group) != gwapiv1.GroupName { + continue + } + if allowedTargetKinds[string(refGrantFrom.Kind)].Has(string(refGrantTo.Kind)) { + return true + } + } + } + + return false +} + func (r *gatewayAPIReconciler) processGateways(ctx context.Context, managedGC *gwapiv1.GatewayClass, resourceTree *resource.Resources, resourceMap *resourceMappings) error { // Find gateways for the managedGC // Find the Gateways that reference this Class. diff --git a/internal/provider/kubernetes/controller_test.go b/internal/provider/kubernetes/controller_test.go index 6b562da47d..bd7b45d37a 100644 --- a/internal/provider/kubernetes/controller_test.go +++ b/internal/provider/kubernetes/controller_test.go @@ -2342,6 +2342,162 @@ func setupReferenceGrantReconciler(objs []client.Object) *gatewayAPIReconciler { return r } +func TestProcessPolicyTargetReferenceGrants(t *testing.T) { + const ( + policyNS = "policy" + gatewayNS = "gateway" + routeNS = "route" + otherNS = "other" + ) + + refGrant := func(name, namespace, fromKind, fromNamespace, toGroup, toKind string) *gwapiv1b1.ReferenceGrant { + return &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Group: gwapiv1b1.Group(egv1a1.GroupVersion.Group), + Kind: gwapiv1b1.Kind(fromKind), + Namespace: gwapiv1b1.Namespace(fromNamespace), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Group: gwapiv1b1.Group(toGroup), + Kind: gwapiv1b1.Kind(toKind), + }, + }, + }, + } + } + + resourceTreeWithTargets := func() *resource.Resources { + resourceTree := resource.NewResources() + resourceTree.Gateways = append(resourceTree.Gateways, + test.GetGateway(types.NamespacedName{Namespace: gatewayNS, Name: "gateway"}, "gc", 80)) + resourceTree.HTTPRoutes = append(resourceTree.HTTPRoutes, + test.GetHTTPRoute( + types.NamespacedName{Namespace: routeNS, Name: "route"}, + "gateway", + test.GetServiceBackendRef(types.NamespacedName{Name: "svc"}, 80), + "")) + return resourceTree + } + + testCases := []struct { + name string + mutateTree func(*resource.Resources) + referenceGrants []client.Object + existingGrantName string + expectedNames sets.Set[string] + }{ + { + name: "BackendTrafficPolicy includes gateway and route target grants", + mutateTree: func(resourceTree *resource.Resources) { + resourceTree.BackendTrafficPolicies = append(resourceTree.BackendTrafficPolicies, &egv1a1.BackendTrafficPolicy{ + ObjectMeta: metav1.ObjectMeta{Namespace: policyNS, Name: "btp"}, + }) + }, + referenceGrants: []client.Object{ + refGrant("btp-gateway", gatewayNS, resource.KindBackendTrafficPolicy, policyNS, gwapiv1.GroupName, resource.KindGateway), + refGrant("btp-route", routeNS, resource.KindBackendTrafficPolicy, policyNS, gwapiv1.GroupName, resource.KindHTTPRoute), + }, + expectedNames: sets.New("btp-gateway", "btp-route"), + }, + { + name: "ClientTrafficPolicy only includes gateway target grants", + mutateTree: func(resourceTree *resource.Resources) { + resourceTree.ClientTrafficPolicies = append(resourceTree.ClientTrafficPolicies, &egv1a1.ClientTrafficPolicy{ + ObjectMeta: metav1.ObjectMeta{Namespace: policyNS, Name: "ctp"}, + }) + }, + referenceGrants: []client.Object{ + refGrant("ctp-gateway", gatewayNS, resource.KindClientTrafficPolicy, policyNS, gwapiv1.GroupName, resource.KindGateway), + refGrant("ctp-route", routeNS, resource.KindClientTrafficPolicy, policyNS, gwapiv1.GroupName, resource.KindHTTPRoute), + }, + expectedNames: sets.New("ctp-gateway"), + }, + { + name: "EnvoyExtensionPolicy includes gateway and route target grants", + mutateTree: func(resourceTree *resource.Resources) { + resourceTree.EnvoyExtensionPolicies = append(resourceTree.EnvoyExtensionPolicies, &egv1a1.EnvoyExtensionPolicy{ + ObjectMeta: metav1.ObjectMeta{Namespace: policyNS, Name: "eep"}, + }) + }, + referenceGrants: []client.Object{ + refGrant("eep-gateway", gatewayNS, resource.KindEnvoyExtensionPolicy, policyNS, gwapiv1.GroupName, resource.KindGateway), + refGrant("eep-route", routeNS, resource.KindEnvoyExtensionPolicy, policyNS, gwapiv1.GroupName, resource.KindHTTPRoute), + }, + expectedNames: sets.New("eep-gateway", "eep-route"), + }, + { + name: "ignores non matching grants", + mutateTree: func(resourceTree *resource.Resources) { + resourceTree.BackendTrafficPolicies = append(resourceTree.BackendTrafficPolicies, &egv1a1.BackendTrafficPolicy{ + ObjectMeta: metav1.ObjectMeta{Namespace: policyNS, Name: "btp"}, + }) + }, + referenceGrants: []client.Object{ + refGrant("wrong-from-kind", gatewayNS, resource.KindClientTrafficPolicy, policyNS, gwapiv1.GroupName, resource.KindGateway), + refGrant("wrong-from-namespace", gatewayNS, resource.KindBackendTrafficPolicy, otherNS, gwapiv1.GroupName, resource.KindGateway), + func() *gwapiv1b1.ReferenceGrant { + rg := refGrant("wrong-from-group", gatewayNS, resource.KindBackendTrafficPolicy, policyNS, gwapiv1.GroupName, resource.KindGateway) + rg.Spec.From[0].Group = gwapiv1b1.Group(gwapiv1.GroupName) + return rg + }(), + refGrant("wrong-to-group", gatewayNS, resource.KindBackendTrafficPolicy, policyNS, corev1.GroupName, resource.KindGateway), + refGrant("outside-target-namespaces", otherNS, resource.KindBackendTrafficPolicy, policyNS, gwapiv1.GroupName, resource.KindGateway), + }, + expectedNames: sets.New[string](), + }, + { + name: "deduplicates grants already collected", + mutateTree: func(resourceTree *resource.Resources) { + resourceTree.BackendTrafficPolicies = append(resourceTree.BackendTrafficPolicies, &egv1a1.BackendTrafficPolicy{ + ObjectMeta: metav1.ObjectMeta{Namespace: policyNS, Name: "btp"}, + }) + }, + referenceGrants: []client.Object{ + refGrant("btp-gateway", gatewayNS, resource.KindBackendTrafficPolicy, policyNS, gwapiv1.GroupName, resource.KindGateway), + }, + existingGrantName: "btp-gateway", + expectedNames: sets.New("btp-gateway"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + resourceTree := resourceTreeWithTargets() + tc.mutateTree(resourceTree) + resourceMap := newResourceMapping() + + if tc.existingGrantName != "" { + for _, obj := range tc.referenceGrants { + refGrant := obj.(*gwapiv1b1.ReferenceGrant) + if refGrant.Name != tc.existingGrantName { + continue + } + resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) + resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) + } + } + + r := setupReferenceGrantReconciler(tc.referenceGrants) + require.NoError(t, r.processPolicyTargetReferenceGrants(t.Context(), resourceTree, resourceMap)) + + actualNames := sets.New[string]() + for _, refGrant := range resourceTree.ReferenceGrants { + actualNames.Insert(refGrant.Name) + } + require.Equal(t, tc.expectedNames, actualNames) + require.Len(t, resourceTree.ReferenceGrants, tc.expectedNames.Len()) + }) + } +} + func TestIsTransientError(t *testing.T) { serverTimeoutErr := kerrors.NewServerTimeout( schema.GroupResource{Group: "core", Resource: "pods"}, "list", 10) diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index f95ef5339a..cacd5c42d8 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -5947,6 +5947,22 @@ _Appears in:_ | `1.3` | TLSv1.3 specifies TLS version 1.3
| +#### TargetNamespaceFrom + +_Underlying type:_ _string_ + + + +_Appears in:_ +- [TargetSelectorNamespaces](#targetselectornamespaces) + +| Value | Description | +| ----- | ----------- | +| `Same` | TargetNamespaceFromSame limits target selection to the policy's namespace.
| +| `All` | TargetNamespaceFromAll allows target selection from all watched namespaces.
| +| `Selector` | TargetNamespaceFromSelector allows target selection from watched namespaces matching the selector.
| + + #### TargetSelector @@ -5964,10 +5980,26 @@ _Appears in:_ | --- | --- | --- | --- | --- | | `group` | _[Group](#group)_ | true | gateway.networking.k8s.io | Group is the group that this selector targets. Defaults to gateway.networking.k8s.io | | `kind` | _[Kind](#kind)_ | true | | Kind is the resource kind that this selector targets. | -| `matchLabels` | _object (keys:string, values:string)_ | false | | MatchLabels are the set of label selectors for identifying the targeted resource | +| `namespaces` | _[TargetSelectorNamespaces](#targetselectornamespaces)_ | false | | Namespaces determines which namespaces are considered for target selection.
If unspecified, only targets in the same namespace as this policy are considered.
When specified, the effective set of namespaces is always constrained to the
namespaces watched by Envoy Gateway. | +| `matchLabels` | _object (keys:string, values:string)_ | false | | MatchLabels are the set of label selectors for identifying the targeted resource. | | `matchExpressions` | _[LabelSelectorRequirement](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#labelselectorrequirement-v1-meta) array_ | false | | MatchExpressions is a list of label selector requirements. The requirements are ANDed. | +#### TargetSelectorNamespaces + + + +TargetSelectorNamespaces determines which namespaces are considered for target selection. + +_Appears in:_ +- [TargetSelector](#targetselector) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `from` | _[TargetNamespaceFrom](#targetnamespacefrom)_ | true | Same | From indicates how namespaces are selected for this target selector.
All means all namespaces watched by Envoy Gateway.
Selector means namespaces watched by Envoy Gateway that match Selector. | +| `selector` | _[LabelSelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#labelselector-v1-meta)_ | false | | Selector selects namespaces when From is set to Selector. | + + #### Timeout diff --git a/site/content/en/v1.4/api/extension_types.md b/site/content/en/v1.4/api/extension_types.md index 9441bf08d0..251a732fd2 100644 --- a/site/content/en/v1.4/api/extension_types.md +++ b/site/content/en/v1.4/api/extension_types.md @@ -1931,6 +1931,20 @@ _Appears in:_ | `after` | _[EnvoyFilter](#envoyfilter)_ | true | | After defines the filter that should come after the filter.
Only one of Before or After must be set. | +#### FromNamespaces + +_Underlying type:_ _string_ + + + +_Appears in:_ +- [TargetSelectorNamespaces](#targetselectornamespaces) + +| Value | Description | +| ----- | ----------- | +| `All` | FromNamespacesAll indicates that the target selector should apply to targets from all namespaces
| + + #### GRPCActiveHealthChecker @@ -4617,6 +4631,20 @@ _Appears in:_ | `matchExpressions` | _[LabelSelectorRequirement](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#labelselectorrequirement-v1-meta) array_ | false | | MatchExpressions is a list of label selector requirements. The requirements are ANDed. | +#### TargetSelectorNamespaces + + + +TargetSelectorNamespaces determines which namespaces are used when selecting policy targets. + +_Appears in:_ +- [TargetSelector](#targetselector) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `from` | _[FromNamespaces](#fromnamespaces)_ | true | | Indicates where targets would be selected for the Policy's TargetSelector. | + + #### Timeout diff --git a/test/e2e/testdata/backendtrafficpolicy-cross-namespace.yaml b/test/e2e/testdata/backendtrafficpolicy-cross-namespace.yaml new file mode 100644 index 0000000000..8222935caa --- /dev/null +++ b/test/e2e/testdata/backendtrafficpolicy-cross-namespace.yaml @@ -0,0 +1,143 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: btp-cross-ns-granted + labels: + policy-target: granted +--- +apiVersion: v1 +kind: Namespace +metadata: + name: btp-cross-ns-denied + labels: + policy-target: denied +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: cross-namespace-btp + namespace: btp-cross-ns-granted +spec: + gatewayClassName: "{GATEWAY_CLASS_NAME}" + listeners: + - name: http + hostname: "granted.cross-namespace-btp.example.com" + port: 80 + protocol: HTTP +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: cross-namespace-btp + namespace: btp-cross-ns-denied +spec: + gatewayClassName: "{GATEWAY_CLASS_NAME}" + listeners: + - name: http + hostname: "denied.cross-namespace-btp.example.com" + port: 80 + protocol: HTTP +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: HTTPRouteFilter +metadata: + name: direct-response-ok + namespace: btp-cross-ns-granted +spec: + directResponse: + body: + type: Inline + inline: "ok" +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: cross-namespace-btp + namespace: btp-cross-ns-granted +spec: + parentRefs: + - name: cross-namespace-btp + hostnames: + - granted.cross-namespace-btp.example.com + rules: + - matches: + - path: + type: PathPrefix + value: / + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-ok +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: cross-namespace-btp + namespace: btp-cross-ns-denied +spec: + parentRefs: + - name: cross-namespace-btp + hostnames: + - denied.cross-namespace-btp.example.com + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: allow-cross-namespace-btp + namespace: btp-cross-ns-granted +spec: + from: + - group: gateway.envoyproxy.io + kind: BackendTrafficPolicy + namespace: gateway-conformance-infra + to: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: cross-namespace-btp +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: cross-namespace-btp-granted + namespace: gateway-conformance-infra +spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespaces: + from: Selector + selector: + matchLabels: + policy-target: granted + faultInjection: + abort: + httpStatus: 418 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: cross-namespace-btp-denied # this BackendTrafficPolicy will be rejected due to missing ReferenceGrant + namespace: gateway-conformance-infra +spec: + targetSelectors: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespaces: + from: Selector + selector: + matchLabels: + policy-target: denied + faultInjection: + abort: + httpStatus: 419 diff --git a/test/e2e/tests/backendtrafficpolicy_cross_namespace.go b/test/e2e/tests/backendtrafficpolicy_cross_namespace.go new file mode 100644 index 0000000000..9b127f4fa9 --- /dev/null +++ b/test/e2e/tests/backendtrafficpolicy_cross_namespace.go @@ -0,0 +1,109 @@ +// 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 e2e + +package tests + +import ( + "testing" + + "k8s.io/apimachinery/pkg/types" + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + + "github.com/envoyproxy/gateway/internal/gatewayapi" + "github.com/envoyproxy/gateway/internal/gatewayapi/resource" +) + +func init() { + ConformanceTests = append(ConformanceTests, BackendTrafficPolicyCrossNamespaceTest) +} + +var BackendTrafficPolicyCrossNamespaceTest = suite.ConformanceTest{ + ShortName: "BackendTrafficPolicyCrossNamespace", + Description: "Test cross-namespace BackendTrafficPolicy attachment to HTTPRoute with and without ReferenceGrant", + Manifests: []string{"testdata/backendtrafficpolicy-cross-namespace.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + t.Run("BackendTrafficPolicyCrossNamespace", func(t *testing.T) { + policyNS := "gateway-conformance-infra" + grantedNS := "btp-cross-ns-granted" + deniedNS := "btp-cross-ns-denied" + + grantedRouteNN := types.NamespacedName{Name: "cross-namespace-btp", Namespace: grantedNS} + deniedRouteNN := types.NamespacedName{Name: "cross-namespace-btp", Namespace: deniedNS} + grantedGatewayNN := types.NamespacedName{Name: "cross-namespace-btp", Namespace: grantedNS} + deniedGatewayNN := types.NamespacedName{Name: "cross-namespace-btp", Namespace: deniedNS} + + grantedAddr := kubernetes.GatewayAndRoutesMustBeAccepted( + t, suite.Client, suite.TimeoutConfig, suite.ControllerName, + kubernetes.NewGatewayRef(grantedGatewayNN), &gwapiv1.HTTPRoute{}, false, grantedRouteNN, + ) + deniedAddr := kubernetes.GatewayAndRoutesMustBeAccepted( + t, suite.Client, suite.TimeoutConfig, suite.ControllerName, + kubernetes.NewGatewayRef(deniedGatewayNN), &gwapiv1.HTTPRoute{}, false, deniedRouteNN, + ) + + kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, grantedRouteNN, grantedGatewayNN) + kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, deniedRouteNN, deniedGatewayNN) + + grantedAncestorRef := gwapiv1.ParentReference{ + Group: gatewayapi.GroupPtr(gwapiv1.GroupName), + Kind: gatewayapi.KindPtr(resource.KindGateway), + Namespace: gatewayapi.NamespacePtr(grantedGatewayNN.Namespace), + Name: gwapiv1.ObjectName(grantedGatewayNN.Name), + } + deniedAncestorRef := gwapiv1.ParentReference{ + Group: gatewayapi.GroupPtr(gwapiv1.GroupName), + Kind: gatewayapi.KindPtr(resource.KindGateway), + Namespace: gatewayapi.NamespacePtr(deniedGatewayNN.Namespace), + Name: gwapiv1.ObjectName(deniedGatewayNN.Name), + } + + BackendTrafficPolicyMustBeAccepted( + t, + suite.Client, + types.NamespacedName{Name: "cross-namespace-btp-granted", Namespace: policyNS}, + suite.ControllerName, + grantedAncestorRef, + ) + + BackendTrafficPolicyMustFail( + t, + suite.Client, + types.NamespacedName{Name: "cross-namespace-btp-denied", Namespace: policyNS}, + suite.ControllerName, + deniedAncestorRef, + "is not permitted by any ReferenceGrant", + ) + + grantedResponse := http.ExpectedResponse{ + Namespace: grantedNS, + Request: http.Request{ + Host: "granted.cross-namespace-btp.example.com", + Path: "/", + }, + Response: http.Response{ + StatusCodes: []int{418}, + }, + } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, grantedAddr, grantedResponse) + + deniedResponse := http.ExpectedResponse{ + Namespace: deniedNS, + Request: http.Request{ + Host: "denied.cross-namespace-btp.example.com", + Path: "/", + }, + Response: http.Response{ + StatusCodes: []int{200}, + }, + } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, deniedAddr, deniedResponse) + }) + }, +} diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 3c3e2be7b4..e874921148 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -25218,8 +25218,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -25227,6 +25301,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array tcpKeepalive: description: |- @@ -26994,8 +27071,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -27003,6 +27154,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array tcpKeepalive: description: |- @@ -29693,8 +29847,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -29702,6 +29930,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array wasm: description: |- @@ -56498,8 +56729,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -56507,6 +56812,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array type: object x-kubernetes-validations: diff --git a/test/helm/gateway-crds-helm/e2e.out.yaml b/test/helm/gateway-crds-helm/e2e.out.yaml index 5077a7985f..b4fb9be5ee 100644 --- a/test/helm/gateway-crds-helm/e2e.out.yaml +++ b/test/helm/gateway-crds-helm/e2e.out.yaml @@ -3191,8 +3191,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -3200,6 +3274,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array tcpKeepalive: description: |- @@ -4967,8 +5044,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -4976,6 +5127,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array tcpKeepalive: description: |- @@ -7666,8 +7820,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -7675,6 +7903,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array wasm: description: |- @@ -34471,8 +34702,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -34480,6 +34785,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array type: object x-kubernetes-validations: diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index bffc9b1497..3dca2ca6ff 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -3191,8 +3191,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -3200,6 +3274,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array tcpKeepalive: description: |- @@ -4967,8 +5044,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -4976,6 +5127,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array tcpKeepalive: description: |- @@ -7666,8 +7820,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -7675,6 +7903,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array wasm: description: |- @@ -34471,8 +34702,82 @@ spec: additionalProperties: type: string description: MatchLabels are the set of label selectors for - identifying the targeted resource + identifying the targeted resource. type: object + namespaces: + description: |- + Namespaces determines which namespaces are considered for target selection. + + If unspecified, only targets in the same namespace as this policy are considered. + + When specified, the effective set of namespaces is always constrained to the + namespaces watched by Envoy Gateway. + properties: + from: + default: Same + description: |- + From indicates how namespaces are selected for this target selector. + + All means all namespaces watched by Envoy Gateway. + Selector means namespaces watched by Envoy Gateway that match Selector. + enum: + - Same + - All + - Selector + type: string + selector: + description: Selector selects namespaces when From is set + to Selector. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + required: + - from + type: object + x-kubernetes-validations: + - message: selector must be specified when from is Selector + rule: self.from != 'Selector' || has(self.selector) required: - kind type: object @@ -34480,6 +34785,9 @@ spec: - message: group must be gateway.networking.k8s.io rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' : true ' + - message: at least one of namespaces, matchLabels, or matchExpressions + must be specified + rule: has(self.namespaces) || has(self.matchLabels) || has(self.matchExpressions) type: array type: object x-kubernetes-validations: