diff --git a/api/v1alpha1/authorization_types.go b/api/v1alpha1/authorization_types.go index 05b5ef59ae..54acaa7512 100644 --- a/api/v1alpha1/authorization_types.go +++ b/api/v1alpha1/authorization_types.go @@ -72,7 +72,7 @@ type Operation struct { // or any other identity that can be extracted from a custom header. // If there are multiple principal types, all principals must match for the rule to match. // -// +kubebuilder:validation:XValidation:rule="(has(self.clientCIDRs) || has(self.jwt) || has(self.headers))",message="at least one of clientCIDRs, jwt, or headers must be specified" +// +kubebuilder:validation:XValidation:rule="(has(self.clientCIDRs) || has(self.jwt) || has(self.headers) || has(self.clientIPGeoLocations))",message="at least one of clientCIDRs, jwt, headers, or clientIPGeoLocations must be specified" type Principal struct { // ClientCIDRs are the IP CIDR ranges of the client. // Valid examples are "192.168.1.0/24" or "2001:db8::/64" @@ -128,6 +128,60 @@ type Principal struct { // +kubebuilder:validation:MinItems=1 // +notImplementedHide SourceCIDRs []CIDR `json:"sourceCIDRs,omitempty"` + + // ClientIPGeoLocations authorizes the request based on geolocation metadata derived from the client IP. + // If multiple entries are specified, one of the ClientIPGeoLocation entries must match for the rule to match. + // + // +optional + // +kubebuilder:validation:MinItems=1 + // +notImplementedHide + ClientIPGeoLocations []ClientIPGeoLocation `json:"clientIPGeoLocations,omitempty"` +} + +// ClientIPGeoLocation specifies geolocation-based match criteria for authorization. +// +// +kubebuilder:validation:XValidation:rule="has(self.country) || has(self.region) || has(self.city) || has(self.asn) || has(self.isp) || has(self.anonymous)",message="at least one of country, region, city, asn, isp, or anonymous must be specified" +type ClientIPGeoLocation struct { + // Country is the country ISO code associated with the client IP. + // + // +optional + // +kubebuilder:validation:MinLength=2 + // +kubebuilder:validation:MaxLength=2 + // +kubebuilder:validation:Pattern=`^[A-Za-z]{2}$` + Country *string `json:"country,omitempty"` + + // Region is the region ISO code associated with the client IP. + // + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=16 + // +kubebuilder:validation:Pattern=`^[A-Za-z0-9-]+$` + Region *string `json:"region,omitempty"` + + // City is the city associated with the client IP. + // + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=128 + City *string `json:"city,omitempty"` + + // ASN is the autonomous system number associated with the client IP. + // + // +optional + // +kubebuilder:validation:Minimum=1 + ASN *uint32 `json:"asn,omitempty"` + + // ISP is the internet service provider associated with the client IP. + // + // +optional + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=256 + ISP *string `json:"isp,omitempty"` + + // Anonymous matches anonymous network detection signals. + // + // +optional + Anonymous *GeoIPAnonymousMatch `json:"anonymous,omitempty"` } // AuthorizationHeaderMatch specifies how to match against the value of an HTTP header within a authorization rule. diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go index 4ce860bd39..ad08058116 100644 --- a/api/v1alpha1/envoyproxy_types.go +++ b/api/v1alpha1/envoyproxy_types.go @@ -198,6 +198,18 @@ type EnvoyProxySpec struct { // +listMapKey=name // +optional DynamicModules []DynamicModuleEntry `json:"dynamicModules,omitempty"` + + // GeoIP defines shared GeoIP provider configuration for this EnvoyProxy fleet. + // + // +optional + // +notImplementedHide + GeoIP *EnvoyProxyGeoIP `json:"geoIP,omitempty"` +} + +// EnvoyProxyGeoIP defines shared GeoIP provider settings for EnvoyProxy. +type EnvoyProxyGeoIP struct { + // Provider defines the GeoIP provider configuration used by GeoIP filter instances. + Provider GeoIPProvider `json:"provider"` } // +kubebuilder:validation:Enum=Strict;InsecureSyntax;Disabled diff --git a/api/v1alpha1/geoip_types.go b/api/v1alpha1/geoip_types.go new file mode 100644 index 0000000000..8e42c47d78 --- /dev/null +++ b/api/v1alpha1/geoip_types.go @@ -0,0 +1,105 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package v1alpha1 + +// GeoIPProvider defines provider-specific settings. +// +kubebuilder:validation:XValidation:rule="self.type == 'MaxMind' ? has(self.maxMind) : true",message="maxMind must be set when type is MaxMind" +type GeoIPProvider struct { + // +kubebuilder:validation:Enum=MaxMind + // +kubebuilder:validation:Required + Type GeoIPProviderType `json:"type"` + + // MaxMind configures the MaxMind provider. + // + // +optional + MaxMind *GeoIPMaxMind `json:"maxMind,omitempty"` +} + +// GeoIPProviderType enumerates GeoIP providers supported by Envoy Gateway. +type GeoIPProviderType string + +const ( + // GeoIPProviderTypeMaxMind configures Envoy with the MaxMind provider pointing to local files. + GeoIPProviderTypeMaxMind GeoIPProviderType = "MaxMind" +) + +// GeoIPMaxMind configures the MaxMind provider. +// These database files are expected to be mounted into the Envoy container, and a sidecar container can be used to update the database files. +// +kubebuilder:validation:XValidation:rule="has(self.cityDbSource) || has(self.countryDbSource) || has(self.asnDbSource) || has(self.ispDbSource) || has(self.anonymousIpDbSource)",message="at least one MaxMind database source must be specified" +type GeoIPMaxMind struct { + // CityDBSource configures the City database source. + // + // +optional + CityDBSource *GeoIPDBSource `json:"cityDbSource,omitempty"` + + // CountryDBSource configures the Country database source. + // + // +optional + CountryDBSource *GeoIPDBSource `json:"countryDbSource,omitempty"` + + // ASNDBSource configures the ASN database source. + // + // +optional + ASNDBSource *GeoIPDBSource `json:"asnDbSource,omitempty"` + + // ISPDBSource configures the ISP database source. + // + // +optional + ISPDBSource *GeoIPDBSource `json:"ispDbSource,omitempty"` + + // AnonymousIPDBSource configures the Anonymous IP database source. + // + // +optional + AnonymousIPDBSource *GeoIPDBSource `json:"anonymousIpDbSource,omitempty"` +} + +// GeoIPDBSource defines where a GeoIP .mmdb database can be loaded from. +type GeoIPDBSource struct { + // Local is a database source from a local file. + Local LocalGeoIPDBSource `json:"local"` +} + +// LocalGeoIPDBSource configures a GeoIP database from a local file path. +type LocalGeoIPDBSource struct { + // Path is the path to the database file. + // + // +kubebuilder:validation:Pattern=`^.*\\.mmdb$` + Path string `json:"path"` +} + +// GeoIPAnonymousMatch matches anonymous network signals emitted by the GeoIP provider. +// If multiple fields are specified, all specified fields must match. +// These signals are not mutually exclusive. A single IP may satisfy multiple +// flags at the same time (for example, a commercial VPN exit IP may also be +// classified as a public proxy, so both IsVPN and IsProxy can be true). +// +// +kubebuilder:validation:XValidation:rule="has(self.isAnonymous) || has(self.isVPN) || has(self.isHosting) || has(self.isTor) || has(self.isProxy)",message="at least one of isAnonymous, isVPN, isHosting, isTor, or isProxy must be specified" +type GeoIPAnonymousMatch struct { + // IsAnonymous matches whether the client IP is considered anonymous. + // + // +optional + IsAnonymous *bool `json:"isAnonymous,omitempty"` + + // IsVPN matches whether the client IP is detected as VPN. + // + // +optional + IsVPN *bool `json:"isVPN,omitempty"` + + // IsHosting matches whether the client IP belongs to a hosting provider. + // + // +optional + IsHosting *bool `json:"isHosting,omitempty"` + + // IsTor matches whether the client IP belongs to a Tor exit node. + // + // +optional + IsTor *bool `json:"isTor,omitempty"` + + // IsProxy matches whether the client IP belongs to a public proxy. + // + // +optional + IsProxy *bool `json:"isProxy,omitempty"` +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ebececcbc9..7f17094f26 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1030,6 +1030,51 @@ func (in *ClientIPDetectionSettings) DeepCopy() *ClientIPDetectionSettings { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClientIPGeoLocation) DeepCopyInto(out *ClientIPGeoLocation) { + *out = *in + if in.Country != nil { + in, out := &in.Country, &out.Country + *out = new(string) + **out = **in + } + if in.Region != nil { + in, out := &in.Region, &out.Region + *out = new(string) + **out = **in + } + if in.City != nil { + in, out := &in.City, &out.City + *out = new(string) + **out = **in + } + if in.ASN != nil { + in, out := &in.ASN, &out.ASN + *out = new(uint32) + **out = **in + } + if in.ISP != nil { + in, out := &in.ISP, &out.ISP + *out = new(string) + **out = **in + } + if in.Anonymous != nil { + in, out := &in.Anonymous, &out.Anonymous + *out = new(GeoIPAnonymousMatch) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientIPGeoLocation. +func (in *ClientIPGeoLocation) DeepCopy() *ClientIPGeoLocation { + if in == nil { + return nil + } + out := new(ClientIPGeoLocation) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClientTLSSettings) DeepCopyInto(out *ClientTLSSettings) { *out = *in @@ -2675,6 +2720,22 @@ func (in *EnvoyProxyAncestorStatus) DeepCopy() *EnvoyProxyAncestorStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnvoyProxyGeoIP) DeepCopyInto(out *EnvoyProxyGeoIP) { + *out = *in + in.Provider.DeepCopyInto(&out.Provider) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyProxyGeoIP. +func (in *EnvoyProxyGeoIP) DeepCopy() *EnvoyProxyGeoIP { + if in == nil { + return nil + } + out := new(EnvoyProxyGeoIP) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvoyProxyHostProvider) DeepCopyInto(out *EnvoyProxyHostProvider) { *out = *in @@ -2880,6 +2941,11 @@ func (in *EnvoyProxySpec) DeepCopyInto(out *EnvoyProxySpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.GeoIP != nil { + in, out := &in.GeoIP, &out.GeoIP + *out = new(EnvoyProxyGeoIP) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyProxySpec. @@ -3484,6 +3550,122 @@ func (in *GatewayAPISettings) DeepCopy() *GatewayAPISettings { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GeoIPAnonymousMatch) DeepCopyInto(out *GeoIPAnonymousMatch) { + *out = *in + if in.IsAnonymous != nil { + in, out := &in.IsAnonymous, &out.IsAnonymous + *out = new(bool) + **out = **in + } + if in.IsVPN != nil { + in, out := &in.IsVPN, &out.IsVPN + *out = new(bool) + **out = **in + } + if in.IsHosting != nil { + in, out := &in.IsHosting, &out.IsHosting + *out = new(bool) + **out = **in + } + if in.IsTor != nil { + in, out := &in.IsTor, &out.IsTor + *out = new(bool) + **out = **in + } + if in.IsProxy != nil { + in, out := &in.IsProxy, &out.IsProxy + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeoIPAnonymousMatch. +func (in *GeoIPAnonymousMatch) DeepCopy() *GeoIPAnonymousMatch { + if in == nil { + return nil + } + out := new(GeoIPAnonymousMatch) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GeoIPDBSource) DeepCopyInto(out *GeoIPDBSource) { + *out = *in + out.Local = in.Local +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeoIPDBSource. +func (in *GeoIPDBSource) DeepCopy() *GeoIPDBSource { + if in == nil { + return nil + } + out := new(GeoIPDBSource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GeoIPMaxMind) DeepCopyInto(out *GeoIPMaxMind) { + *out = *in + if in.CityDBSource != nil { + in, out := &in.CityDBSource, &out.CityDBSource + *out = new(GeoIPDBSource) + **out = **in + } + if in.CountryDBSource != nil { + in, out := &in.CountryDBSource, &out.CountryDBSource + *out = new(GeoIPDBSource) + **out = **in + } + if in.ASNDBSource != nil { + in, out := &in.ASNDBSource, &out.ASNDBSource + *out = new(GeoIPDBSource) + **out = **in + } + if in.ISPDBSource != nil { + in, out := &in.ISPDBSource, &out.ISPDBSource + *out = new(GeoIPDBSource) + **out = **in + } + if in.AnonymousIPDBSource != nil { + in, out := &in.AnonymousIPDBSource, &out.AnonymousIPDBSource + *out = new(GeoIPDBSource) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeoIPMaxMind. +func (in *GeoIPMaxMind) DeepCopy() *GeoIPMaxMind { + if in == nil { + return nil + } + out := new(GeoIPMaxMind) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GeoIPProvider) DeepCopyInto(out *GeoIPProvider) { + *out = *in + if in.MaxMind != nil { + in, out := &in.MaxMind, &out.MaxMind + *out = new(GeoIPMaxMind) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeoIPProvider. +func (in *GeoIPProvider) DeepCopy() *GeoIPProvider { + if in == nil { + return nil + } + out := new(GeoIPProvider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GlobalRateLimit) DeepCopyInto(out *GlobalRateLimit) { *out = *in @@ -5154,6 +5336,21 @@ func (in *LocalDynamicModuleSource) DeepCopy() *LocalDynamicModuleSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalGeoIPDBSource) DeepCopyInto(out *LocalGeoIPDBSource) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalGeoIPDBSource. +func (in *LocalGeoIPDBSource) DeepCopy() *LocalGeoIPDBSource { + if in == nil { + return nil + } + out := new(LocalGeoIPDBSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalJWKS) DeepCopyInto(out *LocalJWKS) { *out = *in @@ -5855,6 +6052,13 @@ func (in *Principal) DeepCopyInto(out *Principal) { *out = make([]CIDR, len(*in)) copy(*out, *in) } + if in.ClientIPGeoLocations != nil { + in, out := &in.ClientIPGeoLocations, &out.ClientIPGeoLocations + *out = make([]ClientIPGeoLocation, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Principal. diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml index feb15e887e..168e1c18f1 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -545,6 +545,130 @@ spec: rule: (has(self.before) && !has(self.after)) || (!has(self.before) && has(self.after)) type: array + geoIP: + description: GeoIP defines shared GeoIP provider configuration for + this EnvoyProxy fleet. + properties: + provider: + description: Provider defines the GeoIP provider configuration + used by GeoIP filter instances. + properties: + maxMind: + description: MaxMind configures the MaxMind provider. + properties: + anonymousIpDbSource: + description: AnonymousIPDBSource configures the Anonymous + IP database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + asnDbSource: + description: ASNDBSource configures the ASN database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + cityDbSource: + description: CityDBSource configures the City database + source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + countryDbSource: + description: CountryDBSource configures the Country database + source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + ispDbSource: + description: ISPDBSource configures the ISP database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + type: object + x-kubernetes-validations: + - message: at least one MaxMind database source must be specified + rule: has(self.cityDbSource) || has(self.countryDbSource) + || has(self.asnDbSource) || has(self.ispDbSource) || has(self.anonymousIpDbSource) + type: + description: GeoIPProviderType enumerates GeoIP providers + supported by Envoy Gateway. + enum: + - MaxMind + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: maxMind must be set when type is MaxMind + rule: 'self.type == ''MaxMind'' ? has(self.maxMind) : true' + required: + - provider + type: object ipFamily: description: |- IPFamily specifies the IP family for the EnvoyProxy fleet. 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 c7bac74c16..fe19156032 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 @@ -272,6 +272,85 @@ spec: type: string minItems: 1 type: array + clientIPGeoLocations: + description: |- + ClientIPGeoLocations authorizes the request based on geolocation metadata derived from the client IP. + If multiple entries are specified, one of the ClientIPGeoLocation entries must match for the rule to match. + items: + description: ClientIPGeoLocation specifies geolocation-based + match criteria for authorization. + properties: + anonymous: + description: Anonymous matches anonymous network + detection signals. + properties: + isAnonymous: + description: IsAnonymous matches whether the + client IP is considered anonymous. + type: boolean + isHosting: + description: IsHosting matches whether the + client IP belongs to a hosting provider. + type: boolean + isProxy: + description: IsProxy matches whether the client + IP belongs to a public proxy. + type: boolean + isTor: + description: IsTor matches whether the client + IP belongs to a Tor exit node. + type: boolean + isVPN: + description: IsVPN matches whether the client + IP is detected as VPN. + type: boolean + type: object + x-kubernetes-validations: + - message: at least one of isAnonymous, isVPN, + isHosting, isTor, or isProxy must be specified + rule: has(self.isAnonymous) || has(self.isVPN) + || has(self.isHosting) || has(self.isTor) + || has(self.isProxy) + asn: + description: ASN is the autonomous system number + associated with the client IP. + format: int32 + minimum: 1 + type: integer + city: + description: City is the city associated with + the client IP. + maxLength: 128 + minLength: 1 + type: string + country: + description: Country is the country ISO code associated + with the client IP. + maxLength: 2 + minLength: 2 + pattern: ^[A-Za-z]{2}$ + type: string + isp: + description: ISP is the internet service provider + associated with the client IP. + maxLength: 256 + minLength: 1 + type: string + region: + description: Region is the region ISO code associated + with the client IP. + maxLength: 16 + minLength: 1 + pattern: ^[A-Za-z0-9-]+$ + type: string + type: object + x-kubernetes-validations: + - message: at least one of country, region, city, + asn, isp, or anonymous must be specified + rule: has(self.country) || has(self.region) || has(self.city) + || has(self.asn) || has(self.isp) || has(self.anonymous) + minItems: 1 + type: array headers: description: |- Headers authorize the request based on user identity extracted from custom headers. @@ -414,9 +493,10 @@ spec: type: array type: object x-kubernetes-validations: - - message: at least one of clientCIDRs, jwt, or headers - must be specified - rule: (has(self.clientCIDRs) || has(self.jwt) || has(self.headers)) + - message: at least one of clientCIDRs, jwt, headers, or + clientIPGeoLocations must be specified + rule: (has(self.clientCIDRs) || has(self.jwt) || has(self.headers) + || has(self.clientIPGeoLocations)) required: - action - principal diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml index c3a43484c4..994302717f 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -544,6 +544,130 @@ spec: rule: (has(self.before) && !has(self.after)) || (!has(self.before) && has(self.after)) type: array + geoIP: + description: GeoIP defines shared GeoIP provider configuration for + this EnvoyProxy fleet. + properties: + provider: + description: Provider defines the GeoIP provider configuration + used by GeoIP filter instances. + properties: + maxMind: + description: MaxMind configures the MaxMind provider. + properties: + anonymousIpDbSource: + description: AnonymousIPDBSource configures the Anonymous + IP database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + asnDbSource: + description: ASNDBSource configures the ASN database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + cityDbSource: + description: CityDBSource configures the City database + source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + countryDbSource: + description: CountryDBSource configures the Country database + source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + ispDbSource: + description: ISPDBSource configures the ISP database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + type: object + x-kubernetes-validations: + - message: at least one MaxMind database source must be specified + rule: has(self.cityDbSource) || has(self.countryDbSource) + || has(self.asnDbSource) || has(self.ispDbSource) || has(self.anonymousIpDbSource) + type: + description: GeoIPProviderType enumerates GeoIP providers + supported by Envoy Gateway. + enum: + - MaxMind + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: maxMind must be set when type is MaxMind + rule: 'self.type == ''MaxMind'' ? has(self.maxMind) : true' + required: + - provider + type: object ipFamily: description: |- IPFamily specifies the IP family for the EnvoyProxy fleet. 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 6a08796189..b10bcf458d 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 @@ -271,6 +271,85 @@ spec: type: string minItems: 1 type: array + clientIPGeoLocations: + description: |- + ClientIPGeoLocations authorizes the request based on geolocation metadata derived from the client IP. + If multiple entries are specified, one of the ClientIPGeoLocation entries must match for the rule to match. + items: + description: ClientIPGeoLocation specifies geolocation-based + match criteria for authorization. + properties: + anonymous: + description: Anonymous matches anonymous network + detection signals. + properties: + isAnonymous: + description: IsAnonymous matches whether the + client IP is considered anonymous. + type: boolean + isHosting: + description: IsHosting matches whether the + client IP belongs to a hosting provider. + type: boolean + isProxy: + description: IsProxy matches whether the client + IP belongs to a public proxy. + type: boolean + isTor: + description: IsTor matches whether the client + IP belongs to a Tor exit node. + type: boolean + isVPN: + description: IsVPN matches whether the client + IP is detected as VPN. + type: boolean + type: object + x-kubernetes-validations: + - message: at least one of isAnonymous, isVPN, + isHosting, isTor, or isProxy must be specified + rule: has(self.isAnonymous) || has(self.isVPN) + || has(self.isHosting) || has(self.isTor) + || has(self.isProxy) + asn: + description: ASN is the autonomous system number + associated with the client IP. + format: int32 + minimum: 1 + type: integer + city: + description: City is the city associated with + the client IP. + maxLength: 128 + minLength: 1 + type: string + country: + description: Country is the country ISO code associated + with the client IP. + maxLength: 2 + minLength: 2 + pattern: ^[A-Za-z]{2}$ + type: string + isp: + description: ISP is the internet service provider + associated with the client IP. + maxLength: 256 + minLength: 1 + type: string + region: + description: Region is the region ISO code associated + with the client IP. + maxLength: 16 + minLength: 1 + pattern: ^[A-Za-z0-9-]+$ + type: string + type: object + x-kubernetes-validations: + - message: at least one of country, region, city, + asn, isp, or anonymous must be specified + rule: has(self.country) || has(self.region) || has(self.city) + || has(self.asn) || has(self.isp) || has(self.anonymous) + minItems: 1 + type: array headers: description: |- Headers authorize the request based on user identity extracted from custom headers. @@ -413,9 +492,10 @@ spec: type: array type: object x-kubernetes-validations: - - message: at least one of clientCIDRs, jwt, or headers - must be specified - rule: (has(self.clientCIDRs) || has(self.jwt) || has(self.headers)) + - message: at least one of clientCIDRs, jwt, headers, or + clientIPGeoLocations must be specified + rule: (has(self.clientCIDRs) || has(self.jwt) || has(self.headers) + || has(self.clientIPGeoLocations)) required: - action - principal diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index e91bda51d6..43584586b2 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -450,7 +450,8 @@ func validateSecurityPolicyForTCP(p *egv1a1.SecurityPolicy) error { if p.Spec.Authorization == nil || len(p.Spec.Authorization.Rules) == 0 { return nil } - for i, rule := range p.Spec.Authorization.Rules { + for i := range p.Spec.Authorization.Rules { + rule := &p.Spec.Authorization.Rules[i] if rule.Principal.JWT != nil { return fmt.Errorf("rule %d: JWT not supported for TCP", i) } @@ -2086,7 +2087,8 @@ func (t *Translator) buildAuthorization(policy *egv1a1.SecurityPolicy) (*ir.Auth } irAuth.DefaultAction = defaultAction - for i, rule := range authorization.Rules { + for i := range authorization.Rules { + rule := &authorization.Rules[i] irPrincipal := ir.Principal{} for _, cidr := range rule.Principal.ClientCIDRs { diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index c366d3b0c5..10d45cb266 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -724,6 +724,25 @@ _Appears in:_ | `customHeader` | _[CustomHeaderExtensionSettings](#customheaderextensionsettings)_ | false | | CustomHeader provides configuration for determining the client IP address for a request based on
a trusted custom HTTP header. This uses the custom_header original IP detection extension.
Refer to https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto
for more details. | +#### ClientIPGeoLocation + + + +ClientIPGeoLocation specifies geolocation-based match criteria for authorization. + +_Appears in:_ +- [Principal](#principal) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `country` | _string_ | false | | Country is the country ISO code associated with the client IP. | +| `region` | _string_ | false | | Region is the region ISO code associated with the client IP. | +| `city` | _string_ | false | | City is the city associated with the client IP. | +| `asn` | _integer_ | false | | ASN is the autonomous system number associated with the client IP. | +| `isp` | _string_ | false | | ISP is the internet service provider associated with the client IP. | +| `anonymous` | _[GeoIPAnonymousMatch](#geoipanonymousmatch)_ | false | | Anonymous matches anonymous network detection signals. | + + #### ClientTLSSettings @@ -1855,6 +1874,20 @@ _Appears in:_ +#### EnvoyProxyGeoIP + + + +EnvoyProxyGeoIP defines shared GeoIP provider settings for EnvoyProxy. + +_Appears in:_ +- [EnvoyProxySpec](#envoyproxyspec) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `provider` | _[GeoIPProvider](#geoipprovider)_ | true | | Provider defines the GeoIP provider configuration used by GeoIP filter instances. | + + #### EnvoyProxyHostProvider @@ -2381,6 +2414,90 @@ _Appears in:_ | `enabled` | _[GatewayAPI](#gatewayapi) array_ | true | | | +#### GeoIPAnonymousMatch + + + +GeoIPAnonymousMatch matches anonymous network signals emitted by the GeoIP provider. +If multiple fields are specified, all specified fields must match. +These signals are not mutually exclusive. A single IP may satisfy multiple +flags at the same time (for example, a commercial VPN exit IP may also be +classified as a public proxy, so both IsVPN and IsProxy can be true). + +_Appears in:_ +- [ClientIPGeoLocation](#clientipgeolocation) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `isAnonymous` | _boolean_ | false | | IsAnonymous matches whether the client IP is considered anonymous. | +| `isVPN` | _boolean_ | false | | IsVPN matches whether the client IP is detected as VPN. | +| `isHosting` | _boolean_ | false | | IsHosting matches whether the client IP belongs to a hosting provider. | +| `isTor` | _boolean_ | false | | IsTor matches whether the client IP belongs to a Tor exit node. | +| `isProxy` | _boolean_ | false | | IsProxy matches whether the client IP belongs to a public proxy. | + + +#### GeoIPDBSource + + + +GeoIPDBSource defines where a GeoIP .mmdb database can be loaded from. + +_Appears in:_ +- [GeoIPMaxMind](#geoipmaxmind) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `local` | _[LocalGeoIPDBSource](#localgeoipdbsource)_ | true | | Local is a database source from a local file. | + + +#### GeoIPMaxMind + + + +GeoIPMaxMind configures the MaxMind provider. +These database files are expected to be mounted into the Envoy container, and a sidecar container can be used to update the database files. + +_Appears in:_ +- [GeoIPProvider](#geoipprovider) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `cityDbSource` | _[GeoIPDBSource](#geoipdbsource)_ | false | | CityDBSource configures the City database source. | +| `countryDbSource` | _[GeoIPDBSource](#geoipdbsource)_ | false | | CountryDBSource configures the Country database source. | +| `asnDbSource` | _[GeoIPDBSource](#geoipdbsource)_ | false | | ASNDBSource configures the ASN database source. | +| `ispDbSource` | _[GeoIPDBSource](#geoipdbsource)_ | false | | ISPDBSource configures the ISP database source. | +| `anonymousIpDbSource` | _[GeoIPDBSource](#geoipdbsource)_ | false | | AnonymousIPDBSource configures the Anonymous IP database source. | + + +#### GeoIPProvider + + + +GeoIPProvider defines provider-specific settings. + +_Appears in:_ +- [EnvoyProxyGeoIP](#envoyproxygeoip) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `type` | _[GeoIPProviderType](#geoipprovidertype)_ | true | | | +| `maxMind` | _[GeoIPMaxMind](#geoipmaxmind)_ | false | | MaxMind configures the MaxMind provider. | + + +#### GeoIPProviderType + +_Underlying type:_ _string_ + +GeoIPProviderType enumerates GeoIP providers supported by Envoy Gateway. + +_Appears in:_ +- [GeoIPProvider](#geoipprovider) + +| Value | Description | +| ----- | ----------- | +| `MaxMind` | GeoIPProviderTypeMaxMind configures Envoy with the MaxMind provider pointing to local files.
| + + #### GlobalRateLimit @@ -3529,6 +3646,20 @@ _Appears in:_ | `path` | _string_ | true | | Path is the absolute filesystem path to the dynamic module shared library (.so file). | +#### LocalGeoIPDBSource + + + +LocalGeoIPDBSource configures a GeoIP database from a local file path. + +_Appears in:_ +- [GeoIPDBSource](#geoipdbsource) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `path` | _string_ | true | | Path is the path to the database file. | + + #### LocalJWKS diff --git a/test/cel-validation/securitypolicy_test.go b/test/cel-validation/securitypolicy_test.go index d05ecc4b9e..52c86cfbc4 100644 --- a/test/cel-validation/securitypolicy_test.go +++ b/test/cel-validation/securitypolicy_test.go @@ -1301,7 +1301,7 @@ func TestSecurityPolicyTarget(t *testing.T) { }, } }, - wantErrors: []string{"at least one of clientCIDRs, jwt, or headers must be specified"}, + wantErrors: []string{"at least one of clientCIDRs, jwt, headers, or clientIPGeoLocations must be specified"}, }, { desc: "authorization-jwt-claims-without-jwt-authn", diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 649c584d89..1574085396 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -30892,6 +30892,130 @@ spec: rule: (has(self.before) && !has(self.after)) || (!has(self.before) && has(self.after)) type: array + geoIP: + description: GeoIP defines shared GeoIP provider configuration for + this EnvoyProxy fleet. + properties: + provider: + description: Provider defines the GeoIP provider configuration + used by GeoIP filter instances. + properties: + maxMind: + description: MaxMind configures the MaxMind provider. + properties: + anonymousIpDbSource: + description: AnonymousIPDBSource configures the Anonymous + IP database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + asnDbSource: + description: ASNDBSource configures the ASN database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + cityDbSource: + description: CityDBSource configures the City database + source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + countryDbSource: + description: CountryDBSource configures the Country database + source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + ispDbSource: + description: ISPDBSource configures the ISP database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + type: object + x-kubernetes-validations: + - message: at least one MaxMind database source must be specified + rule: has(self.cityDbSource) || has(self.countryDbSource) + || has(self.asnDbSource) || has(self.ispDbSource) || has(self.anonymousIpDbSource) + type: + description: GeoIPProviderType enumerates GeoIP providers + supported by Envoy Gateway. + enum: + - MaxMind + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: maxMind must be set when type is MaxMind + rule: 'self.type == ''MaxMind'' ? has(self.maxMind) : true' + required: + - provider + type: object ipFamily: description: |- IPFamily specifies the IP family for the EnvoyProxy fleet. @@ -48164,6 +48288,85 @@ spec: type: string minItems: 1 type: array + clientIPGeoLocations: + description: |- + ClientIPGeoLocations authorizes the request based on geolocation metadata derived from the client IP. + If multiple entries are specified, one of the ClientIPGeoLocation entries must match for the rule to match. + items: + description: ClientIPGeoLocation specifies geolocation-based + match criteria for authorization. + properties: + anonymous: + description: Anonymous matches anonymous network + detection signals. + properties: + isAnonymous: + description: IsAnonymous matches whether the + client IP is considered anonymous. + type: boolean + isHosting: + description: IsHosting matches whether the + client IP belongs to a hosting provider. + type: boolean + isProxy: + description: IsProxy matches whether the client + IP belongs to a public proxy. + type: boolean + isTor: + description: IsTor matches whether the client + IP belongs to a Tor exit node. + type: boolean + isVPN: + description: IsVPN matches whether the client + IP is detected as VPN. + type: boolean + type: object + x-kubernetes-validations: + - message: at least one of isAnonymous, isVPN, + isHosting, isTor, or isProxy must be specified + rule: has(self.isAnonymous) || has(self.isVPN) + || has(self.isHosting) || has(self.isTor) + || has(self.isProxy) + asn: + description: ASN is the autonomous system number + associated with the client IP. + format: int32 + minimum: 1 + type: integer + city: + description: City is the city associated with + the client IP. + maxLength: 128 + minLength: 1 + type: string + country: + description: Country is the country ISO code associated + with the client IP. + maxLength: 2 + minLength: 2 + pattern: ^[A-Za-z]{2}$ + type: string + isp: + description: ISP is the internet service provider + associated with the client IP. + maxLength: 256 + minLength: 1 + type: string + region: + description: Region is the region ISO code associated + with the client IP. + maxLength: 16 + minLength: 1 + pattern: ^[A-Za-z0-9-]+$ + type: string + type: object + x-kubernetes-validations: + - message: at least one of country, region, city, + asn, isp, or anonymous must be specified + rule: has(self.country) || has(self.region) || has(self.city) + || has(self.asn) || has(self.isp) || has(self.anonymous) + minItems: 1 + type: array headers: description: |- Headers authorize the request based on user identity extracted from custom headers. @@ -48306,9 +48509,10 @@ spec: type: array type: object x-kubernetes-validations: - - message: at least one of clientCIDRs, jwt, or headers - must be specified - rule: (has(self.clientCIDRs) || has(self.jwt) || has(self.headers)) + - message: at least one of clientCIDRs, jwt, headers, or + clientIPGeoLocations must be specified + rule: (has(self.clientCIDRs) || has(self.jwt) || has(self.headers) + || has(self.clientIPGeoLocations)) required: - action - principal 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 f795861ef0..2e07265107 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -8873,6 +8873,130 @@ spec: rule: (has(self.before) && !has(self.after)) || (!has(self.before) && has(self.after)) type: array + geoIP: + description: GeoIP defines shared GeoIP provider configuration for + this EnvoyProxy fleet. + properties: + provider: + description: Provider defines the GeoIP provider configuration + used by GeoIP filter instances. + properties: + maxMind: + description: MaxMind configures the MaxMind provider. + properties: + anonymousIpDbSource: + description: AnonymousIPDBSource configures the Anonymous + IP database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + asnDbSource: + description: ASNDBSource configures the ASN database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + cityDbSource: + description: CityDBSource configures the City database + source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + countryDbSource: + description: CountryDBSource configures the Country database + source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + ispDbSource: + description: ISPDBSource configures the ISP database source. + properties: + local: + description: Local is a database source from a local + file. + properties: + path: + description: Path is the path to the database + file. + pattern: ^.*\\.mmdb$ + type: string + required: + - path + type: object + required: + - local + type: object + type: object + x-kubernetes-validations: + - message: at least one MaxMind database source must be specified + rule: has(self.cityDbSource) || has(self.countryDbSource) + || has(self.asnDbSource) || has(self.ispDbSource) || has(self.anonymousIpDbSource) + type: + description: GeoIPProviderType enumerates GeoIP providers + supported by Envoy Gateway. + enum: + - MaxMind + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: maxMind must be set when type is MaxMind + rule: 'self.type == ''MaxMind'' ? has(self.maxMind) : true' + required: + - provider + type: object ipFamily: description: |- IPFamily specifies the IP family for the EnvoyProxy fleet. @@ -26145,6 +26269,85 @@ spec: type: string minItems: 1 type: array + clientIPGeoLocations: + description: |- + ClientIPGeoLocations authorizes the request based on geolocation metadata derived from the client IP. + If multiple entries are specified, one of the ClientIPGeoLocation entries must match for the rule to match. + items: + description: ClientIPGeoLocation specifies geolocation-based + match criteria for authorization. + properties: + anonymous: + description: Anonymous matches anonymous network + detection signals. + properties: + isAnonymous: + description: IsAnonymous matches whether the + client IP is considered anonymous. + type: boolean + isHosting: + description: IsHosting matches whether the + client IP belongs to a hosting provider. + type: boolean + isProxy: + description: IsProxy matches whether the client + IP belongs to a public proxy. + type: boolean + isTor: + description: IsTor matches whether the client + IP belongs to a Tor exit node. + type: boolean + isVPN: + description: IsVPN matches whether the client + IP is detected as VPN. + type: boolean + type: object + x-kubernetes-validations: + - message: at least one of isAnonymous, isVPN, + isHosting, isTor, or isProxy must be specified + rule: has(self.isAnonymous) || has(self.isVPN) + || has(self.isHosting) || has(self.isTor) + || has(self.isProxy) + asn: + description: ASN is the autonomous system number + associated with the client IP. + format: int32 + minimum: 1 + type: integer + city: + description: City is the city associated with + the client IP. + maxLength: 128 + minLength: 1 + type: string + country: + description: Country is the country ISO code associated + with the client IP. + maxLength: 2 + minLength: 2 + pattern: ^[A-Za-z]{2}$ + type: string + isp: + description: ISP is the internet service provider + associated with the client IP. + maxLength: 256 + minLength: 1 + type: string + region: + description: Region is the region ISO code associated + with the client IP. + maxLength: 16 + minLength: 1 + pattern: ^[A-Za-z0-9-]+$ + type: string + type: object + x-kubernetes-validations: + - message: at least one of country, region, city, + asn, isp, or anonymous must be specified + rule: has(self.country) || has(self.region) || has(self.city) + || has(self.asn) || has(self.isp) || has(self.anonymous) + minItems: 1 + type: array headers: description: |- Headers authorize the request based on user identity extracted from custom headers. @@ -26287,9 +26490,10 @@ spec: type: array type: object x-kubernetes-validations: - - message: at least one of clientCIDRs, jwt, or headers - must be specified - rule: (has(self.clientCIDRs) || has(self.jwt) || has(self.headers)) + - message: at least one of clientCIDRs, jwt, headers, or + clientIPGeoLocations must be specified + rule: (has(self.clientCIDRs) || has(self.jwt) || has(self.headers) + || has(self.clientIPGeoLocations)) required: - action - principal