Skip to content

Commit

Permalink
Add RateLimitFilter support in k8s provider (#908)
Browse files Browse the repository at this point in the history
* Add RateLimitFilter support in k8s provider

Relates to #670

Signed-off-by: Arko Dasgupta <arko@tetrate.io>

* fix k8s error

Signed-off-by: Arko Dasgupta <arko@tetrate.io>

* edit crd

Signed-off-by: Arko Dasgupta <arko@tetrate.io>

* add another k8s test

Signed-off-by: Arko Dasgupta <arko@tetrate.io>

* more tests && add crd

Signed-off-by: Arko Dasgupta <arko@tetrate.io>

Signed-off-by: Arko Dasgupta <arko@tetrate.io>
  • Loading branch information
arkodg authored Jan 18, 2023
1 parent 38b80a2 commit 5b8b24e
Show file tree
Hide file tree
Showing 15 changed files with 653 additions and 100 deletions.
28 changes: 18 additions & 10 deletions api/v1alpha1/ratelimitfilter_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
// KindRateLimitFilter is the name of the RateLimitFilter kind.
KindRateLimitFilter = "RateLimitFilter"
)

// +kubebuilder:object:root=true
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`

// RateLimitFilter allows the user to limit the number of incoming requests
// to a predefined value based on attributes within the traffic flow.
Expand All @@ -22,15 +26,6 @@ type RateLimitFilter struct {
Spec RateLimitFilterSpec `json:"spec"`
}

// +kubebuilder:object:root=true

// RateLimitFilterList contains a list of RateLimitFilter resources.
type RateLimitFilterList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []RateLimitFilter `json:"items"`
}

// RateLimitFilterSpec defines the desired state of RateLimitFilter.
// +union
type RateLimitFilterSpec struct {
Expand Down Expand Up @@ -162,3 +157,16 @@ type RateLimitValue struct {
//
// +kubebuilder:validation:Enum=Second;Minute;Hour;Day
type RateLimitUnit string

//+kubebuilder:object:root=true

// RateLimitFilterList contains a list of RateLimitFilter resources.
type RateLimitFilterList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []RateLimitFilter `json:"items"`
}

func init() {
SchemeBuilder.Register(&RateLimitFilter{}, &RateLimitFilterList{})
}
8 changes: 5 additions & 3 deletions internal/gatewayapi/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,12 @@ func ValidateHTTPRouteFilter(filter *v1beta1.HTTPRouteFilter) error {
return errors.New("extensionRef field must be specified for an extended filter")
case string(filter.ExtensionRef.Group) != egv1a1.GroupVersion.Group:
return fmt.Errorf("invalid group; must be %s", egv1a1.GroupVersion.Group)
case string(filter.ExtensionRef.Kind) != egv1a1.KindAuthenticationFilter:
return fmt.Errorf("invalid kind; must be %s", egv1a1.KindAuthenticationFilter)
default:
case string(filter.ExtensionRef.Kind) == egv1a1.KindAuthenticationFilter:
return nil
case string(filter.ExtensionRef.Kind) == egv1a1.KindRateLimitFilter:
return nil
default:
return fmt.Errorf("unknown %s kind", string(filter.ExtensionRef.Kind))
}
}

Expand Down
12 changes: 12 additions & 0 deletions internal/gatewayapi/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ func TestValidateAuthenFilterRef(t *testing.T) {
},
expected: true,
},
{
name: "valid rateLimitfilter",
filter: &gwapiv1b1.HTTPRouteFilter{
Type: gwapiv1b1.HTTPRouteFilterExtensionRef,
ExtensionRef: &gwapiv1b1.LocalObjectReference{
Group: gwapiv1b1.Group(egv1a1.GroupVersion.Group),
Kind: egv1a1.KindRateLimitFilter,
Name: "test",
},
},
expected: true,
},
}

for _, tc := range testCases {
Expand Down
46 changes: 24 additions & 22 deletions internal/gatewayapi/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,34 @@ type InfraIRMap map[string]*ir.Infra
// resources that the translators needs as inputs.
// +k8s:deepcopy-gen=true
type Resources struct {
Gateways []*v1beta1.Gateway
HTTPRoutes []*v1beta1.HTTPRoute
GRPCRoutes []*v1alpha2.GRPCRoute
TLSRoutes []*v1alpha2.TLSRoute
TCPRoutes []*v1alpha2.TCPRoute
UDPRoutes []*v1alpha2.UDPRoute
ReferenceGrants []*v1alpha2.ReferenceGrant
Namespaces []*v1.Namespace
Services []*v1.Service
Secrets []*v1.Secret
AuthenFilters []*egv1a1.AuthenticationFilter
EnvoyProxy *egcfgv1a1.EnvoyProxy
Gateways []*v1beta1.Gateway
HTTPRoutes []*v1beta1.HTTPRoute
GRPCRoutes []*v1alpha2.GRPCRoute
TLSRoutes []*v1alpha2.TLSRoute
TCPRoutes []*v1alpha2.TCPRoute
UDPRoutes []*v1alpha2.UDPRoute
ReferenceGrants []*v1alpha2.ReferenceGrant
Namespaces []*v1.Namespace
Services []*v1.Service
Secrets []*v1.Secret
AuthenFilters []*egv1a1.AuthenticationFilter
RateLimitFilters []*egv1a1.RateLimitFilter
EnvoyProxy *egcfgv1a1.EnvoyProxy
}

func NewResources() *Resources {
return &Resources{
Gateways: []*v1beta1.Gateway{},
HTTPRoutes: []*v1beta1.HTTPRoute{},
GRPCRoutes: []*v1alpha2.GRPCRoute{},
TLSRoutes: []*v1alpha2.TLSRoute{},
Services: []*v1.Service{},
Secrets: []*v1.Secret{},
ReferenceGrants: []*v1alpha2.ReferenceGrant{},
Namespaces: []*v1.Namespace{},
AuthenFilters: []*egv1a1.AuthenticationFilter{},
EnvoyProxy: new(egcfgv1a1.EnvoyProxy),
Gateways: []*v1beta1.Gateway{},
HTTPRoutes: []*v1beta1.HTTPRoute{},
GRPCRoutes: []*v1alpha2.GRPCRoute{},
TLSRoutes: []*v1alpha2.TLSRoute{},
Services: []*v1.Service{},
Secrets: []*v1.Secret{},
ReferenceGrants: []*v1alpha2.ReferenceGrant{},
Namespaces: []*v1.Namespace{},
AuthenFilters: []*egv1a1.AuthenticationFilter{},
RateLimitFilters: []*egv1a1.RateLimitFilter{},
EnvoyProxy: new(egcfgv1a1.EnvoyProxy),
}
}

Expand Down
11 changes: 11 additions & 0 deletions internal/gatewayapi/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ spec:
singular: ratelimitfilter
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
- name: v1alpha1
schema:
openAPIV3Schema:
description: RateLimitFilter allows the user to limit the number of incoming
Expand Down Expand Up @@ -158,4 +154,3 @@ spec:
type: object
served: true
storage: true
subresources: {}
3 changes: 2 additions & 1 deletion internal/provider/kubernetes/config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
resources:
- bases/config.gateway.envoyproxy.io_envoyproxies.yaml
- bases/gateway.envoyproxy.io_authenticationfilters.yaml
#+kubebuilder:scaffold:crdkustomizeresource
- bases/gateway.envoyproxy.io_ratelimitfilters.yaml
#+kubebuilder:scaffold:crdkustomizeresource

patchesStrategicMerge:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
Expand Down
1 change: 1 addition & 0 deletions internal/provider/kubernetes/config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ rules:
- gateway.envoyproxy.io
resources:
- authenticationfilters
- ratelimitfilters
verbs:
- get
- list
Expand Down
116 changes: 82 additions & 34 deletions internal/provider/kubernetes/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,21 @@ import (
)

const (
classGatewayIndex = "classGatewayIndex"
gatewayTLSRouteIndex = "gatewayTLSRouteIndex"
gatewayHTTPRouteIndex = "gatewayHTTPRouteIndex"
gatewayGRPCRouteIndex = "gatewayGRPCRouteIndex"
gatewayTCPRouteIndex = "gatewayTCPRouteIndex"
gatewayUDPRouteIndex = "gatewayUDPRouteIndex"
secretGatewayIndex = "secretGatewayIndex"
targetRefGrantRouteIndex = "targetRefGrantRouteIndex"
serviceHTTPRouteIndex = "serviceHTTPRouteIndex"
serviceGRPCRouteIndex = "serviceGRPCRouteIndex"
serviceTLSRouteIndex = "serviceTLSRouteIndex"
serviceTCPRouteIndex = "serviceTCPRouteIndex"
serviceUDPRouteIndex = "serviceUDPRouteIndex"
authenFilterHTTPRouteIndex = "authenHTTPRouteIndex"
classGatewayIndex = "classGatewayIndex"
gatewayTLSRouteIndex = "gatewayTLSRouteIndex"
gatewayHTTPRouteIndex = "gatewayHTTPRouteIndex"
gatewayGRPCRouteIndex = "gatewayGRPCRouteIndex"
gatewayTCPRouteIndex = "gatewayTCPRouteIndex"
gatewayUDPRouteIndex = "gatewayUDPRouteIndex"
secretGatewayIndex = "secretGatewayIndex"
targetRefGrantRouteIndex = "targetRefGrantRouteIndex"
serviceHTTPRouteIndex = "serviceHTTPRouteIndex"
serviceGRPCRouteIndex = "serviceGRPCRouteIndex"
serviceTLSRouteIndex = "serviceTLSRouteIndex"
serviceTCPRouteIndex = "serviceTCPRouteIndex"
serviceUDPRouteIndex = "serviceUDPRouteIndex"
authenFilterHTTPRouteIndex = "authenHTTPRouteIndex"
rateLimitFilterHTTPRouteIndex = "rateLimitHTTPRouteIndex"
)

type gatewayAPIReconciler struct {
Expand Down Expand Up @@ -103,6 +104,9 @@ type resourceMappings struct {
// authenFilters is a map of AuthenticationFilters, where the key is the
// namespaced name of the AuthenticationFilter.
authenFilters map[types.NamespacedName]*egv1a1.AuthenticationFilter
// rateLimitFilters is a map of RateLimitFilters, where the key is the
// namespaced name of the RateLimitFilter.
rateLimitFilters map[types.NamespacedName]*egv1a1.RateLimitFilter
}

func newResourceMapping() *resourceMappings {
Expand All @@ -111,6 +115,7 @@ func newResourceMapping() *resourceMappings {
allAssociatedBackendRefs: map[types.NamespacedName]struct{}{},
allAssociatedRefGrants: map[types.NamespacedName]*gwapiv1a2.ReferenceGrant{},
authenFilters: map[types.NamespacedName]*egv1a1.AuthenticationFilter{},
rateLimitFilters: map[types.NamespacedName]*egv1a1.RateLimitFilter{},
}
}

Expand Down Expand Up @@ -357,6 +362,15 @@ func (r *gatewayAPIReconciler) getAuthenticationFilters(ctx context.Context) ([]
return authenList.Items, nil
}

func (r *gatewayAPIReconciler) getRateLimitFilters(ctx context.Context) ([]egv1a1.RateLimitFilter, error) {
rateLimitList := new(egv1a1.RateLimitFilterList)
if err := r.client.List(ctx, rateLimitList); err != nil {
return nil, fmt.Errorf("failed to list RateLimitFilters: %v", err)
}

return rateLimitList.Items, nil
}

func (r *gatewayAPIReconciler) processGateways(ctx context.Context, acceptedGC *gwapiv1b1.GatewayClass, resourceMap *resourceMappings, resourceTree *gatewayapi.Resources) error {
// Find gateways for the acceptedGC
// Find the Gateways that reference this Class.
Expand Down Expand Up @@ -474,7 +488,7 @@ func addReferenceGrantIndexers(ctx context.Context, mgr manager.Manager) error {
// addHTTPRouteIndexers adds indexing on HTTPRoute.
// - For Service objects that are referenced in HTTPRoute objects via `.spec.rules.backendRefs`.
// This helps in querying for HTTPRoutes that are affected by a particular Service CRUD.
// - For AuthenticationFilter objects that are referenced in HTTPRoute objects via
// - For AuthenticationFilter and RateLimitFilter objects that are referenced in HTTPRoute objects via
// `.spec.rules[].filters`. This helps in querying for HTTPRoutes that are affected by a
// particular AuthenticationFilter CRUD.
func addHTTPRouteIndexers(ctx context.Context, mgr manager.Manager) error {
Expand All @@ -486,30 +500,56 @@ func addHTTPRouteIndexers(ctx context.Context, mgr manager.Manager) error {
return err
}

if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1b1.HTTPRoute{}, authenFilterHTTPRouteIndex, func(obj client.Object) []string {
httproute := obj.(*gwapiv1b1.HTTPRoute)
var filters []string
for _, rule := range httproute.Spec.Rules {
for i := range rule.Filters {
filter := rule.Filters[i]
if filter.Type == gwapiv1b1.HTTPRouteFilterExtensionRef {
if err := gatewayapi.ValidateHTTPRouteFilter(&filter); err != nil {
filters = append(filters,
types.NamespacedName{
Namespace: httproute.Namespace,
Name: string(filter.ExtensionRef.Name),
}.String(),
)
}
if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1b1.HTTPRoute{}, authenFilterHTTPRouteIndex, authenFilterHTTPRouteIndexFunc); err != nil {
return err
}

if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1b1.HTTPRoute{}, rateLimitFilterHTTPRouteIndex, rateLimitFilterHTTPRouteIndexFunc); err != nil {
return err
}
return nil
}

func authenFilterHTTPRouteIndexFunc(rawObj client.Object) []string {
httproute := rawObj.(*gwapiv1b1.HTTPRoute)
var filters []string
for _, rule := range httproute.Spec.Rules {
for i := range rule.Filters {
filter := rule.Filters[i]
if filter.Type == gwapiv1b1.HTTPRouteFilterExtensionRef && string(filter.ExtensionRef.Kind) == egv1a1.KindAuthenticationFilter {
if err := gatewayapi.ValidateHTTPRouteFilter(&filter); err != nil {
filters = append(filters,
types.NamespacedName{
Namespace: httproute.Namespace,
Name: string(filter.ExtensionRef.Name),
}.String(),
)
}
}
}
return filters
}); err != nil {
return err
}
return filters
}

return nil
func rateLimitFilterHTTPRouteIndexFunc(rawObj client.Object) []string {
httproute := rawObj.(*gwapiv1b1.HTTPRoute)
var filters []string
for _, rule := range httproute.Spec.Rules {
for i := range rule.Filters {
filter := rule.Filters[i]
if filter.Type == gwapiv1b1.HTTPRouteFilterExtensionRef && string(filter.ExtensionRef.Kind) == egv1a1.KindRateLimitFilter {
if err := gatewayapi.ValidateHTTPRouteFilter(&filter); err != nil {
filters = append(filters,
types.NamespacedName{
Namespace: httproute.Namespace,
Name: string(filter.ExtensionRef.Name),
}.String(),
)
}
}
}
}
return filters
}

func gatewayHTTPRouteIndexFunc(rawObj client.Object) []string {
Expand Down Expand Up @@ -1133,6 +1173,14 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M
return err
}

// Watch RateLimitFilter CRUDs and enqueue associated HTTPRoute objects.
if err := c.Watch(
&source.Kind{Type: &egv1a1.RateLimitFilter{}},
&handler.EnqueueRequestForObject{},
predicate.NewPredicateFuncs(r.httpRoutesForRateLimitFilter)); err != nil {
return err
}

r.log.Info("watching gatewayAPI related objects")
return nil
}
Expand Down
Loading

0 comments on commit 5b8b24e

Please sign in to comment.